[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*.{c,h,d,di,dd,sh}]\nend_of_line = crlf\ninsert_final_newline = true\nindent_style = space\nindent_size = 4\ntrim_trailing_whitespace = true\ncharset = utf-8\ndfmt_brace_style = allman\ndfmt_soft_max_line_length = 120\ndfmt_align_switch_statements = true\ndfmt_outdent_attributes = true\ndfmt_outdent_labels = true\ndfmt_split_operator_at_line_end = true\ndfmt_space_after_cast = false\ndfmt_space_after_keywords = true\ndfmt_space_before_function_parameters = false\ndfmt_selective_import_space = false\ndfmt_keep_line_breaks = true\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "patreon: gecko0307\nliberapay: gecko0307\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: CI\n\non: \n  push:\n    branches: [ master ]\n    paths:\n    - 'dlib/**'\n    - 'dub.json'\n    - '.github/workflows/**'\n  pull_request:\n    branches: [ master ]\n    paths:\n    - 'dlib/**'\n    - 'dub.json'\n    - '.github/workflows/**'\n\njobs:\n  test:\n    name: Dub Tests\n    strategy:\n      matrix:\n        os: [ubuntu-latest, windows-latest]\n        dc: [dmd-latest, ldc-latest]\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v2\n      - uses: dlang-community/setup-dlang@v1\n        with:\n          compiler: ${{ matrix.dc }}\n      - name: Run tests\n        run: dub test --build=unittest-cov\n      - name: Run code coverage\n        if: success()\n        run: |\n          curl https://codecov.io/bash > codecov.sh\n          bash codecov.sh\n"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled Object files\n*.o\n*.obj\n\n# Compiled Static libraries\n*.a\n*.lib\n\n# Executables\n*.exe\n\n# Dub files\n.dub\ndub.selections.json\n\n# Dub test files\n__test__library__\ntests/\n\n# Documentation\ndocs/\ndocs-doxygen/\ndocs.json\n__dummy.html\ndocumentation.chm\n\n# Coverage files\n*.lst\n\n# Debug info\n*.pdb\n\n# Backup files\n*~\n"
  },
  {
    "path": "AUTHORS.md",
    "content": "# dlib authors and contributors\n* Core library - [Timur Gafarov aka gecko0307](https://github.com/gecko0307)\n* Filesystem package, stream module - [Martin Cejp aka minexew](https://github.com/minexew)\n* Memory package, socket module - [Eugene Wissner aka belka-ew](https://github.com/belka-ew)\n* Image binarization, histogram generation, median filter - [LightHouse Software](http://lhs-blog.info/) / [Oleg Baharev aka aquaratixc](https://github.com/aquaratixc)\n* TGA and BMP encoder, improved BMP decoder, bugfixes, unittests - [Roman Chistokhodov aka FreeSlave](https://github.com/FreeSlave)\n* PNG decoder improvements - [Vadim Lopatin](https://github.com/buggins)\n* Combinatorics module - Nick Papanastasiou\n* Rectangle drawing - [Aaron Nédélec aka ReactiveAlkali](https://github.com/ReactiveAlkali)\n* Vector swizzling assignment - [João Lourenço aka iK4tsu](https://github.com/iK4tsu)\n* SSE vector math port for GDC - [Alexander Perfilyev](https://github.com/aperfilev)\n* Bugfixes - [Andrey Penechko aka MrSmith33](https://github.com/MrSmith33), [Valeriy Fedotov](https://github.com/Valera), [Basile Burg aka SixthDot](https://github.com/SixthDot), [Ate Eskola aka dukc](https://github.com/dukc), [Martin Nowak aka dawg](https://github.com/MartinNowak), [Mathias Lang aka Geod24](https://github.com/Geod24), [Nick Treleaven aka ntrel](https://github.com/ntrel), [Nikolay Krasheninnikov aka GoodNike](https://github.com/GoodNike), [ijet](https://github.com/my-ijet), [TETYYS](https://github.com/TETYYS), [Razvan Nitu aka RazvanN7](https://github.com/RazvanN7), [Denis Feklushkin aka denizzzka](https://github.com/denizzzka)\n* Additional unittests - [Roman Vlasov](https://github.com/VlasovRoman)\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "dlib 1.5.0 - TBD\n----------------\n- **dlib.math**\n  - `trsMatrix` that creates a transformation matrix from translation/rotation/scaling a once, without three matrix multiplications\n  - New easing function `easeOutElastic`.\n\ndlib 1.4.1 - 1 Jan, 2026\n------------------------\n- **dlib.geometry**\n  - Fix setting `OBB.center`.\n\ndlib 1.4.0 - 1 Dec, 2025\n------------------------\nChanges since dlib 1.4.0 beta1:\n- **dlib.math**\n  - `Matrix.isAffine`, `Matrix.inverse`, `Matrix.getColumn`, `Quaternion.toMatrix4x4`, `Quaternion.toMatrix3x3` are now `const`.\n\ndlib 1.4.0 beta1 - 31 Nov, 2025\n-------------------------------\n- **dlib.memory**\n  - `dlib.memory.arena` - a general-purpose region-based memory allocator\n- **dlib.container**\n  - `dlib.container.spscqueue` - wait-free single-producer single-consumer queue\n  - `dlib.container.mappedlist` - a list with string-mapped indices\n- **dlib.math**\n  - New function `wrapAngle` in `dlib.math.utils`.\n\ndlib 1.3.3 - 30 Aug, 2025\n-------------------------\n- **dlib.serialization**\n  - Fix bugs in `dlib.serialization.json`.\n\ndlib 1.3.2 - 1 May, 2025\n------------------------\n- **dlib.math**\n  - `lookAtMatrix` now checks for matching eye and up directions\n  - Fix unittest for vector swizzling.\n\ndlib 1.3.1 - 10 Mar, 2025\n-------------------------\n- **dlib.math**\n  -  Limit `dlib.math.sse` for x86_64 only\n  - `dlib.math.complex.zeta` was removed due to its broken status.\n\ndlib 1.3.0 - 21 Feb, 2023\n-------------------------\nNo changes since dlib 1.3.0 beta1.\n\ndlib 1.3.0 beta1 - 14 Feb, 2023\n-------------------------------\n- **dlib.random**\n  - New module `dlib.random.random` that implements `random`, a pseudo-random number generator based on C `rand`\n- **dlib.math**\n  - Function that computes quadratic Bézier curve - `dlib.math.interpolation.bezierQuadratic`\n  - GNU D Compiler (GDC) support in `dlib.math.sse`\n\ndlib 1.2.1 - 30 Aug, 2023\n-------------------------\n- **dlib.image**\n  - Median filter (`dlib.image.filters.median`)\n- **dlib.filesystem**\n  - Bugfixes in `dlib.filesystem.posix.common`\n- **dlib.core**\n  - `dlib.core.thread`: fix deprecations in Windows multithreading API signatures\n- **dlib.math**\n  - Fix unittest for `dlib.math.utils.nextPowerOfTen`\n- **Misc**\n  - Added `AUTHORS.md`.\n\ndlib 1.2.0 - 30 Apr, 2023\n-------------------------\nNo changes since dlib 1.2.0 beta1.\n\ndlib 1.2.0 beta1 - 19 Apr, 2023\n-------------------------------\n- **dlib.math**\n  - `homothetyMatrix` and `homothetyMatrix2D` functions in `dlib.math.transformation`\n  - `radtorev` and `revtorad` functions in `dlib.math.utils` that convert radians to revolutions and revolutions to radians, respectively\n- **dlib.image**\n  - New funtion `drawRect` in `dlib.image.render.shapes`\n- **Misc**\n  - Added `CODE_OF_CONDUCT.md`\n  - Doxygen support for documentation.\n\ndlib 1.1.0 - 5 Oct, 2022\n-------------------------\nNo changes since dlib 1.1.0 beta1.\n\ndlib 1.1.0 beta1 - 10 Sep, 2022\n-------------------------------\n- **dlib.geometry**\n  - New module `dlib.geometry.mpr` - implementation of the Minkowski Portal Refinement algorithm that detects intersection between two arbitrary convex shapes\n  - New module `dlib.geometry.support` with support functions for some common shapes\n- **dlib.math**\n  - `integer` and `frac` functions in `dlib.math.utils` that return integer part and fractional part of a real number, respectively.\n- **dlib.image**\n  - Fix compilation for x86.\n\ndlib 1.0.0 - 17 Feb, 2022\n-------------------------\nNo changes since dlib 1.0.0 beta2.\n\ndlib 1.0.0 beta2 - 05 Feb, 2022\n-------------------------------\nChanges since dlib 1.0.0 beta1:\n- **dlib.image**\n  - File-based image loading functions now preload data to memory, so that decoders run faster (2x-10x depending on format and image size).\n\ndlib 1.0.0 beta1 - 12 Jan, 2022\n-------------------------------\n- **dlib.core**\n  - **Breaking change:** `dlib.core.bitio.swapEndian16` moved to `dlib.math.utils`\n  - POSIX thread creation is now validated in debug mode\n- **dlib.math**\n  - **Breaking change:** deprecated method `Quaternion.generator` has been removed\n  - **Breaking change:** deprecated functions `sum`, `invertArray`, `allIsZero` in `dlib.math.utils` have been removed\n  - `interpHermiteDerivative`\n  - `interpHermite` now support vector types\n  - `Complexd` alias to `Complex!(double)` in `dlib.math.complex`\n  - Fix `dlib.math.complex.pow`, `dlib.math.complex.atan2`\n- **dlib.geometry**\n  - **Breaking change:** deprecated method `Ray.intersectSphere` with `position` and `radius` arguments has been removed\n  - **Breaking change:** deprecated method `Ray.intersectTriangle` with `v0`, `v1`, `v2` arguments has been removed.\n  - Fix `Triangle.boundingBox`\n  - New function `intrSphereVsAABB` in `dlib.geometry.intersection`\n  - `AABB.intersectsSphere` is deprecated, use `intrSphereVsAABB` instead.\n\ndlib 0.23.0 - 1 Oct, 2021\n-------------------------\nNo changes since dlib 0.23.0 beta1.\n\ndlib 0.23.0 beta1 - 28 Sep, 2021\n--------------------------------\n- **dlib.math**\n  - Vector swizzling assign support: `v.zxy = Vector3f(1, 2, 3);`\n  - `Quaternion.generator` is deprecated, use `Quaternion.rotationAxis` and `Quaternion.rotationAngle` instead\n  - `Quaternion.fromEulerAngles` and `Quaternion.toEulerAngles` now use angles in pitch-yaw-roll format\n  - `EPSILON` in `dlib.math.utils` now equals 0.000001.\n- **dlib.geometry**\n  - `Ray.intersectSphere` with `position` and `radius` arguments is deprecated, use `Ray.intersectSphere` with `Sphere` struct instead\n  - `Ray.intersectTriangle` with `v0`, `v1`, `v2` arguments is is deprecated, use `Ray.intersectTriangle` with `Triangle` struct instead\n- **dlib.image**\n  - `isAlmostZero` for `Color4f`\n- **dlib.filesystem**\n  - `StdFileSystem.openDir` now returns null if path is not a valid directory.\n\ndlib 0.22.0 - 13 Jun, 2021\n--------------------------\nNo changes since dlib 0.22.0 beta1.\n\ndlib 0.22.0 beta1 - 26 May, 2021\n--------------------------------\n- **dlib.core**\n  - **Breaking change:** removed `dlib.core.oop.implements` (non-working function)\n- **dlib.math**\n  - Fix matrix subtraction\n  - Fix wrong bounds check in matrix slice assignment\n  - **Breaking change:** removed `dlib.math.linsolve.solveGS` (non-working function)\n  - Binary matrix operations are now `const`\n  - `sum`, `invertArray`, `allIsZero` in `dlib.math.utils` are deprecated. Use `reduce!((a, b) => a + b)`, `map!(a => -a)`, `reduce!((a, b) => a + b == 0)` instead\n- **dlib.image**\n  - **Breaking change:** deprecated type `dlib.image.image.PixelFormat` has been removed\n  - **Breaking change:** deprecated aliases `save` and `load` in `dlib.audio.io` have been removed\n- **dlib.audio**\n  - **Breaking change:** deprecated aliases `save`, `load`, `saveAnimated`, `loadAnimated`, `saveHDRI`, `loadHDRI` in `dlib.image.io` have been removed\n- **dlib.text**\n  - **Breaking change:** deprecated method `UTF8Decoder.byDChar` have been removed\n  - **Breaking change:** deprecated method `UTF16LEDecoder.byDChar` have been removed\n  - **Breaking change:** deprecated aliases `UTF16Decoder` and `UTF16Encoder` have been removed\n- **dlib.serialization**\n  - Boolean values support in JSON decoder.\n\ndlib 0.21.0 - 7 Apr, 2021\n-------------------------\nNo changes since dlib 0.21.0 beta2.\n\ndlib 0.21.0 beta2 - 28 Mar, 2021\n--------------------------------\nChanges since dlib 0.21.0 beta1:\n- **dlib.image**\n  - `PixelFormat` is deprecated, use `IntegerPixelFormat` instead.\n\ndlib 0.21.0 beta1 - 22 Feb, 2021\n--------------------------------\n- **dlib.text**\n  - **Breaking change:** deprecated module `dlib.text.unmanagedstring` has been removed\n  - **Breaking change:** deprecated method `String.byDChar` has been removed\n  - `UTF16Decoder` and `UTF16Encoder` are deprecated, use `UTF16LEDecoder` and `UTF16LEEncoder` instead\n- **dlib.image**\n  - **Breaking change:** deprecated module `dlib.image.io.io` has been removed\n  - `load` and `save` are deprecated, use `loadImage` and `saveImage` instead\n  - `loadAnimated` and `saveAnimated` are deprecated, use `loadAnimatedImage` and `saveAnimatedImage` instead\n  - `loadHDRI` and `saveHDRI` are deprecated, use `loadHDRImage` and `saveHDRImage` instead\n  - Fix integer overflow in `Image.getPixel` and `Image.setPixel`\n- **dlib.container**\n  - **Breaking change:** deprecated alias `DynamicArray` has been removed\n- **dlib.coding**\n  - **Breaking change:** deprecated module `dlib.coding.hash` has been removed\n- **dlib.audio**\n  - `load` and `save` are deprecated, use `loadSound` and `saveSound` instead\n- **dlib.serialization**\n  - Fix a bug in JSON decoder\n- **Misc**\n  - Switched from Travis CI to GitHub Actions for running integration tests.\n\ndlib 0.20.0 - 16 Oct, 2020\n--------------------------\nNo changes since dlib 0.20.0 beta1.\n\ndlib 0.20.0 beta1 - 10 Oct, 2020\n--------------------------------\n- **dlib.image**\n  - `dlib.image.io.io` is deprecated, import `dlib.image.io` instead\n- **dlib.audio**\n  - New package: `dlib.audio.io`\n- **dlib.text**\n  - `dlib.text.unmanagedstring` is deprecated, use `dlib.text.str` instead\n  - `String.byDChar` is deprecated, use `String.decode` instead\n  - `UTF8Decoder.byDChar` is deprecated, use `UTF8Decoder.decode` instead\n  - `UTF16Decoder.byDChar` is deprecated, use `UTF16Decoder.decode` instead\n- **dlib.coding**\n  - `dlib.coding.hash` is deprecated, use `std.digest` instead\n- **dlib.container**\n  - `dlib.container.array.DynamicArray` is deprecated, use `dlib.container.array.Array` instead\n- **Documentation**\n  - Deploy-ready ddoc documentation for dlib now can be generated from source code using `dub --build=ddox`. It uses [scod](https://code.dlang.org/packages/scod) generator and is hosted [here](https://gecko0307.github.io/dlib/docs/dlib.html). Harbored-mod support has been dropped.\n  - Many modules are now documented better.\n- **Misc**\n  - Added latest DMD (2.094.0, 2.093.1) and LDC (1.23.0) to Travis CI config.\n\ndlib 0.19.2 - 26 Aug, 2020\n--------------------------\n- A couple of fixes for LDC\n- New AppVeyor configuration.\n\ndlib 0.19.1 - 24 July, 2020\n---------------------------\n- **dlib.network**\n  - Fixed compilation under Windows\n- **dlib.filesystem**\n  - `isFile`, `isDir` properties now work for `StdFileSystem` entries\n- **dlib.container**\n  - `DynamicArray.readOnlyData`\n- **dlib.text**\n  - `String.toString` and `String.ptr` are now `const`\n- **Misc**\n  - Added latest DMD (2.093.0) and LDC (1.22.0) to Travis CI config.\n\ndlib 0.19.0 - 31 May, 2020\n--------------------------\nChanges since beta2:\n- **dlib.coding**, **dlib.filesystem**\n  - Deprecation fixes\n- **Misc**\n  - Added latest DMD (2.092.0) to Travis CI config.\n\ndlib 0.19.0 beta2 - 22 May, 2020\n--------------------------------\nChanges since beta1:\n- **dlib.math**\n  - Transformation of a vector with 4x4 matrix now doesn't include affinity check.\n  - `dlib.math.transformation.scaling` fix.\n\ndlib 0.19.0 beta1 - 8 May, 2020\n-------------------------------\n- **dlib.core**\n  - New module `dlib.core.mutex`, a thin abstraction over platform-specific thread synchronization primitives.\n  - `Thread.sleep`\n- **dlib.concurrency**\n  - New package that implements a simple thread pool.\n- **dlib.image**\n  - New module `dlib.image.render.text` that provides `drawText`, a function to render ASCII strings on images.\n- **dlib.math**\n  - **Breaking change:** deprecated modules `dlib.math.easing`, `dlib.math.smoothstep` have been removed.\n  - **Breaking change:** tuple constructor of `Vector` now implicitly extends the last argument to all remaining components if the tuple is smaller than vector. This ensures e.g. `Vector3f(0) == Vector3f(0, 0, 0)`.\n- **dlib.geometry**\n  - **Breaking change:** deprecated modules `dlib.geometry.bezier`, `dlib.geometry.hermite` have been removed.\n- **Breaking change:** deprecated package `dlib.functional` has been removed.\n- **dlib.text**\n  - `UTF16Encoder` in `dlib.text.utf16`.\n  - `String` can now be constructed directly from `InputStream`.\n  - **Breaking change:** deprecated property `String.cString` has been removed.\n- **dlib.container**\n  - `Queue` and `Stack` now use `DynamicArray` internally instead of `LinkedList`.\n- **Misc**\n  - Added latest DMD (2.091.1, 2.090.1) and LDC (1.21.0, 1.20.0) to Travis CI config.\n\ndlib 0.18.0 - 28 Feb, 2020\n--------------------------\nNo changes since dlib 0.18.0 beta1.\n\ndlib 0.18.0 beta1 - 23 Feb, 2020\n--------------------------------\n- **dlib.math**\n  - All interpolation functions moved to `dlib.math.interpolation`, which is now a package import. It includes `nearest`, `linear`, `bezier`, `catmullrom`, `hermite`, `smoothstep`, `easing` modules. Corresponding old modules (`dlib.math.easing`, `dlib.math.smoothstep`, `dlib.geometry.bezier`, `dlib.geometry.hermite`) are deprecated.\n- **dlib.audio**\n  - `SawtoothWaveSynth`, `TriangleWaveSynth` in `dlib.audio.synth`.\n- **dlib.text**\n  - `String` is now always null-terminated.\n- **dlib.functional**\n  - The whole package is now deprecated.\n  - `dlib.functional.hof` module moved to `dlib.math.hof`.\n  - `dlib.functional.range` module is deprecated, use `std.range.iota` instead.\n- **Misc**\n  - Added latest DMD (2.090.1, 2.089.1) and LDC (1.19.0, 1.18.0) to Travis CI config.\n\ndlib 0.17.0 - 21 Oct, 2019\n--------------------------\nChanges since beta:\n- **dlib.container**\n  - `dlib.container.array`: iterating over array via `foreach_reverse`.\n\ndlib 0.17.0 beta1 - 5 Oct, 2019\n-------------------------------\n- **dlib.core**\n  - `BufferedStreamReader` in `dlib.core.stream` - a simple input range to read fixed chunks of data from an `InputStream`.\n- **dlib.image**\n  - **Breaking change:** `dlib.image.compleximage` has been removed.\n  - `dlib.image.signal2d` is now fully GC-free.\n  - Filtering of indexed images in PNG decoder is now supported (#142).\n- **dlib.math**\n  - `dlib.math.tensor` now uses `dlib.core.memory` for internal allocations.\n  - **Breaking change:** deprecated function `identityQuaternion` has been removed. Use `Quaternion.identity` instead.\n- **dlib.geometry**\n  - **Breaking change:** deprecated aliases `bezierCurveFunc2D` and `bezierCurveFunc3D` have been removed. Use `bezierVector2` and `bezierVector3` instead.\n- **Misc**\n  - Added latest DMD (2.088.0, 2.087.1) and LDC (1.17.0, 1.16.0) to Travis CI config.\n\ndlib 0.16.0 - 30 Mar, 2019\n--------------------------\nNo changes since dlib 0.16.0 beta1.\n\ndlib 0.16.0 beta1 - 4 Mar, 2019\n-------------------------------\n- **dlib.core**\n  - `dlib.core.memory`: Memory profiler now reports file and line of each allocation. Now it is enabled in runtime using `enableMemoryProfiler` function.\n  - `dlib.core.memory`: `Owner.deleteOwnedObject`.\n- **dlib.text**\n  - `dlib.text.lexer`: `Lexer.position`.\n  - `dlib.text.unmanagedstring`: `String.cString`.\n  - **Breaking change:** deprecated module `dlib.text.slicelexer` has been removed.\n- **dlib.container**\n  - `dlib.container.array`: `DynamicArray.removeFirst`.\n  - **Breaking change:** deprecated module `dlib.container.hash` has been removed.\n- **dlib.image**\n  - **Breaking change:** deprecated module `dlib.image.parallel` has been removed.\n- **dlib.math**\n  - New module `dlib.math.easing` - some basic easing functions for fancy interpolation.\n  - **Breaking change:** deprecated module `dlib.math.fixed` has been removed.\n  - `dlib.math.quaternion`: fixed a bug in `Quaternion.rotationAxis`.\n- **dlib.geometry**\n  - `dlib.geometry.trimesh` not doesn't use GC.\n- **dlib.serialization**\n  - `dlib.serialization.json` - GC-free JSON parser.\n- **dlib.functional**\n  - **Breaking change:** deprecated module `dlib.functional.combinators`, `dlib.functional.quantifiers` has been removed.\n  - **Breaking change:** deprecated `map`, `filter`, `reduce` functions from `dlib.functional.range` have been removed.\n- **Misc**\n  - Added latest DMD (2.085.0, 2.084.1) and LDC (1.13.0, 1.14.0) to Travis CI config.\n\ndlib 0.15.0 - 9 Nov, 2018\n-------------------------\n- **dlib.container**\n  - `opSlice` and `$` for `dlib.container.array.DynamicArray`.\n\ndlib 0.15.0 beta1 - 4 Nov, 2018\n-------------------------------\n- **dlib.core**\n  - **Breaking change**: temporarily removed `dlib.core.fiber` due to lacking Windows support. It is now in [fiber branch](https://github.com/gecko0307/dlib/tree/fiber) until finished.\n- **dlib.text**\n  - New module `dlib.text.unmanagedstring` that provides `String`, a GC-free UTF8 string type based on `DynamicArray`.\n  - `UTF16Decoder` in `dlib.text.utf16`, `UTF8Encoder` in `dlib.text.utf8`.\n  - `dlib.text.encodings` - a one-stop solution for handling text encodings in generic way. Decoder is any range that outputs `dchar`, encoder is any object that defines `size_t encode(dchar, ubyte[])`.\n  - `dlib.text.common` that defines `DECODE_END` and `DECODE_ERROR` for decoders to use.\n- **dlib.container**\n  - `reserve` and `resize` for `dlib.container.array.DynamicArray` (#151).\n  - **Breaking change**: deprecated `dlib.container.aarray` module has been removed. Use `dlib.container.dict` instead.\n- **dlib.image**\n  - Fixed unintentional fallthrough in `dlib.image.io.saveImage` that caused error on TGA image.\n  - More accurate path filling in `dlib.image.canvas`.\n  - `dlib.image.parallel` has been deprecated.\n- **dlib.math**\n  - **Breaking change**: deprecated `dlib.math.affine` module has been removed. Use `dlib.math.transformation` instead.\n  - `dlib.math.fixed` has been deprecated.\n- **dlib.functional**\n  - `dlib.functional.quantifiers` has been deprecated.\n  - Free functions in `dlib.functional.range` (`map`, `filter`, `reduce`) have been deprecated. Use corresponding Phobos functions instead.\n- **Misc**\n  - dlib now can be built with recent GNU D Compiler (GDC). **Note:** `dlib.math.sse` is not supported with GDC.\n  - Added latest DMD (2.083.0, 2.082.1) and LDC (1.12.0) to Travis CI config.\n  - Support for [harbored-mod](https://github.com/dlang-community/harbored-mod) documentation generator.\n\ndlib 0.14.0 - 9 Jul, 2018\n-------------------------\nNo changes since dlib 0.14.0 beta1.\n\ndlib 0.14.0 beta1 - 8 Jul, 2018\n-------------------------------\n**Important:** dlib now officially doesn't support macOS. This is an act of protest against [Apple's drop of OpenGL support](https://developer.apple.com/macos/whats-new/#deprecationofopenglandopencl). While you probably still can use dlib's platform-independent and Posix-based functionality under macOS, there's no guarantee that this will continue, and compatibility issues will not be addressed. Read detailed manifesto [here](https://github.com/gecko0307/dlib/wiki/Why-dlib-doesn%27t-support-macOS%3F).\n\n- **dlib.image**\n  - **Breaking change:** `SuperImage.pixelFormat` now returns `uint` instead of `PixelFormat`. This allows extending dlib with custom pixel formats while maintaining compatibility with `PixelFormat`. Values from 0 to 255 are reserved for dlib, values 256 and above are application-specific. This change is just a new convention and will not break any existing logics, though explicit cast to `PixelFormat` may be required in some cases. Comparisons such as `img.pixelFormat == PixelFormat.RGB8` will work fine.\n  - `PixelFormat.RGBA_FLOAT` is now deprecated, use `FloatPixelFormat.RGBAF32` from `dlib.image.hdri` instead.\n  - Saving to HDR is now supported (`saveHDR` functions in `dlib.image.io.hdr`).\n  - New filters: `dlib.image.filters.histogram` (generates an image histogram) and `dlib.image.filters.binarization` (image thresholding using Otsu's method).\n  - ACES tonemapper (`hdrTonemapACES`) and average luminance function (`averageLuminance`) in `dlib.image.hdri`.\n  - Improved `dlib.image.canvas`. Path rasterizer now natively does anti-aliasing. Fixed bug with rendering on non-square images.\n- **dlib.audio**\n  - Synthesizer framework (`dlib.audio.synth`). It allows to write synthesizers and use them to 'render' sounds, like in DAWs. Three built-in synthesizers are available: `SineWaveSynth`, `SquareWaveSynth`, `FMSynth`. To write actual data to `Sound` objects, two functions are available: `fillSynth` and `mixSynth`.\n- **dlib.math**\n  - New module `dlib.math.smoothstep` with sigmoid-like functions: `hermiteSmoothstep`, `rationalSmoothstep`.\n- **dlib.core**\n  - DMD 2.081.0 compatibility fix in `dlib.core.stream`.\n- **Misc**\n  - Added latest DMD (2.081.0, 2.080.1) and LDC (1.10.0) to Travis CI config. CI builds for macOS were stopped for reason mentioned above.\n\ndlib 0.13.0 - 14 May, 2018\n--------------------------\nNo changes since dlib 0.13.0 beta1.\n\ndlib 0.13.0 beta1 - 9 May, 2018\n-------------------------------\n- **dlib.async** has been removed for security reasons. Currently there are no active contributors to maintain the package and fix bugs, so it is considered not safe to use due to potential data corruption or loss. There's [async branch](https://github.com/gecko0307/dlib/tree/async) for those who still want to use it, but for new projects it is strongly recommended to consider using more actively developed alternatives, such as [vibe-core](https://code.dlang.org/packages/vibe-core) or [Tanya](https://code.dlang.org/packages/tanya).\n- **dlib.image**\n  - New module `dlib.image.canvas` that provides `Canvas` class, a vector graphics engine inspired by HTML5 canvas. Currently it supports rasterizing arbitrary polygons and cubic Bezier paths, filled and outlined. It renders to user-provided `SuperImage`.\n  - Improved HDR file decoder. Now it supports HDR files with magic string `#?RGBE`.\n  - Reinhard and Hable tonemappers in `dlib.image.hdri`: `hdrTonemapReinhard` and `hdrTonemapHable`.\n  - New filters in `dlib.image.filters.edgedetect`: `edgeDetectLaplace` and `edgeDetectSobel`.\n  - New methods for `Color4f`: `toLinear` and `toGamma`.\n  - Fixed bugs in `dlib.image.arithmetics` module.\n- **dlib.math**\n  - New functions in `dlib.math.vector`: `reflect`, `refract`, `faceforward`.\n  - New functions in `dlib.math.utils`: `min2` and `max2`.\n- **dlib.geometry**\n  - New functions in `dlib.geometry.bezier`: `bezierTangentVector2` and `bezierTangentVector3`.\n- **Misc**\n  - Added latest DMD (2.080.0, 2.079.1) and LDC (1.9.0, 1.8.0) to Travis CI config.\n  - dlib now does CI under Windows using [AppVeyor](https://www.appveyor.com/).\n\ndlib 0.12.2 - 7 Nov, 2017\n-------------------------\n* Enum constants of type `Vector` now can be assigned to variables.\n* Naming of functions in `dlib.geometry.bezier` is changed. `bezier` function is now `bezierCubic`, `bezierCurveFunc2D` is `bezierVector2`, `bezierCurveFunc3D` is `bezierVector3`. There are aliases with old names for backward compatibility.\n\ndlib 0.12.1 - 28 Oct, 2017\n--------------------------\n* Fixed loading 16-bit PNG images\n* Corrected Bézier function.\n\ndlib 0.12.0 - 16 Oct, 2017\n--------------------------\nNo changes since dlib 0.12.0 beta1.\n\ndlib 0.12.0 beta1 - 9 Oct, 2017\n-------------------------------\n- **dlib.core**\n  - New module `dlib.core.ownership` - a Delphi-like object ownership system. Objects are registered to parent object, which automatically deletes them when gets deleted itself. In many cases this can be a convenient trade-off between fully automatic and fully manual memory management.\n  - New module `dlib.core.fiber` - initial fibers implementation (Linux-only for now).\n- **dlib.container**\n  - Containers now use Phobos-conforming method names. Old names are still supported via aliases.\n  - `DynamicArray` now supports inserting and removing values by arbitrary indices (`insertKey` and `removeKey`).\n  - `~=` operator support for `LinkedList`.\n  - Full unittest coverage of `dlib.container.array`.\n  - More unittests for `dlib.container.dict`.\n- **dlib.image**\n  - New class `UnmanagedAnimatedImage` - GC-free counterpart of `AnimatedImage`.\n  - **Breaking change:** `dlib.image.tone.contrast` is now `dlib.image.filters.contrast`.\n  - `dlib.image.fthread` is now based on `dlib.core.thread`.\n- **dlib.filesystem**\n  - File access rights in `FileStat`.\n  - Nanosecond modification time precision support in `stat` under Posix.\n- **dlib.math**\n  - New direct solver (`solve`) in `dlib.math.linsolve` based on LUP decomposition.\n- **dlib.geometry**\n  - Frustum-sphere intersection test (`intersectsSphere`) for `dlib.geometry.frustum`.\n- **dlib.coding**\n  - **Breaking change:** `dlib.coding.huffman` is merged with `dlib.image.io.jpeg`.\n- **Misc**\n  - Added latest DMD (2.075.1, 2.076.0) and LDC (1.3.0, 1.4.0) to Travis CI config.\n\ndlib 0.11.1 - 24 May, 2017\n--------------------------\n* Added `alphaOver` in `dlib.image.color`\n* Fixed memory leak in `dlib.image.io.png`\n* Deprecation fix: use `dlib.math.transformation` everywhere instead of `dlib.math.affine`.\n\ndlib 0.11.0 - 3 May, 2017\n-------------------------\nChanges from beta:\n* Merged `idct.d` with `jpeg.d`, use `dlib.math.transformation` in `dlib.image.transform`\n* Added `hdrTonemapAverageLuminance` to `dlib.image.hdri`\n* Fixed memory leak in HDR decoder\n\ndlib 0.11.0 beta1 - 25 Apr, 2017\n--------------------------------\n- **dlib.core**\n  - `New` and `Delete` in `dlib.core.memory` are now based on allocators from `dlib.memory`. By default `Mallocator` is used. It is possible to switch global allocator.\n- **dlib.memory**\n  - Added `GCallocator`, an allocator based on on D's built-in garbage collector.\n- **dlib.image**\n  - Full-featured APNG support in `dlib.image.io.png` with dispose and blend operations. Saving animations to APNG is also supported.\n- **dlib.filesystem**\n  - Added `traverseDir`, GC-free recursive directory scanner.\n- **dlib.math**\n  - `distance` and `distancesqr` overloads for 2D vectors.\n  - `dlib.math.affine` is now deprecated. `dlib.math.transformation` should be used instead.\n- **dlib.async**\n  - Fixed segfault in event loop.\n- **Misc**\n  - Removed deprecated `dlib.xml` package. `dlib.serialization.xml` should be used instead.\n  - Added latest DMD (2.074.0) and LDC (1.2.0) to Travis CI config.\n  - A new logo and homepage for the project: https://gecko0307.github.io/dlib.\n\ndlib 0.10.1 - 14 Mar, 2017\n--------------------------\n* Animated images and basic APNG support (unfinished, without dispose and blend operations, saving to APNG is also missing)\n* Fixed some bugs in `dlib.text.slicelexer` and `dlib.serialization.xml`. `dlib.text.lexer.Lexer` is now an alias to `dlib.text.slicelexer.SliceLexer`\n* Added latest DMD (2.073.2) and LDC (1.1.0) to Travis CI config.\n\ndlib 0.10.0 - 23 Jan, 2017\n--------------------------\nChanges from beta:\n- 64-bit fix in `dlib.network.socket` under Windows\n- Unittest fix in `dlib.filesystem.local`\n- Code cleanup, use consistent line endings and indentations everywhere\n- EditorConfig support\n- dlib now compiles with DMD 2.073.0 and LDC 1.1.0-beta6.\n\ndlib 0.10.0 beta1 - 13 Jan, 2017\n--------------------------------\n- **dlib.async** - this new package provides a cross-platform event loop and asynchronous programming capabilities. It can be used to implement asynchronous servers. Under the hood the package is based on different multiplexing APIs: Epoll on Linux, IOCP on Windows, and Kqueue on BSD / OSX\n- **dlib.memory** - new tools and interfaces to generalize memory allocation. There is `Allocator` interface, similar to Phobos' `IAllocator`, but simpler. There are also several implementations of this interface: `Mallocator` (malloc based allocator) and `MmapPool` (block based allocator for Posix systems with mmap/munmap support).\n- **dlib.serialization** - a new home for XML (and, hopefully, other markup languages in future). `dlib.xml` is deprecated, but left with public imports for compatibility purpose\n  - XML parser (`dlib.serialization.xml`) is now fully GC-free\n- **dlib.network**\n  - `dlib.network.socket`, a cross-platform socket API. Supports Windows and Posix\n- **dlib.image**\n  - Breaking change: redesign of `dlib.image.hdri` module. Now it supports manual memory allocation and has its own image factories. Also implemented simple tone mapping tool based on gamma compression to convert HDR images to LDR\n  - Radiance HDR/RGBE format support (only loading for now)\n- **dlib.container**\n  - New module - `dlib.container.buffer`, an interface for input/output buffers\n  - Fixed some issues in `dlib.container.array`\n- **dlib.text**\n  - Improved `SliceLexer` (fixed bug with multicharacter delimiters)\n  - Added `dlib.text.utils.immutableCopy`\n- **dlib.math**\n  - `dlib.math.vector.normal` is now `dlib.math.vector.planeNormal`\n- **Other improvements**\n  - Added latest DMD (2.072.2) to Travis CI config.\n\nMany thanks to [Eugene Wissner](https://github.com/belka-ew) for implementing `dlib.async`, `dlib.memory` and `dlib.network`.\n\ndlib 0.9.2 - 11 Jun, 2016\n-------------------------\n- Fixed building with DMD 2.071.1\n\ndlib 0.9.1 - 9 Jun, 2016\n------------------------\n- Added `SliceLexer` in `dlib.text`\n- Fixed wrong `opApply` in `DynamicArray` and `Trie`\n\ndlib 0.9.0 - 23 May, 2016\n-------------------------\nChanges from beta:\n- Bugfix and unittests for `ArrayStream`\n- Fixed loading of 32-bit BMP with bitfield masks.\n\ndlib 0.9.0 beta1 - 14 May, 2016\n-------------------------------\n- dlib.network\n  - A new package for networking. So far it contains only one module, `dlib.network.url` - an URL parser\n- dlib.image\n  - 2-dimensional iteration for images. Also there are now `ImageRegion` and `ImageWindowRange` that simplify writing kernel filters\n  - `dlib.image.transform` module implements affine transformations for images: translation, rotation and scaling. Transformation with arbitrary 3x3 matrix is also possible\n  - Improved BMP and TGA support: new color modes and RLE8 for BMP, saving BMP and TGA\n  - Improved `boxBlur`\n  - `getPixel` and `setPixe` in `Image` class are now public\n- dlib.math\n  - New `dlib.math.tensor` module implements generic multidimensional array, both with static and dynamic memory allocation\n- dlib.container\n  - Improved `LinkedList`, added range interface. Added unittests for `LinkedList` and `DynamicArray`\n- dlib.text\n  - `UTF8Decoder` and `Lexer` now support range interface. Added unittests for both\n- Other improvements\n  - Added latest DMD (2.071.0) to Travis CI config, added DUB service files to .gitignore.\n\ndlib 0.8.1 - 13 Feb, 2016\n-------------------------\nMinor bugfix release: `saveWav` in `dlib.audio.io.wav` now uses `Sound` interface instead of `GenericSound` class.\n\ndlib 0.8.0 - 12 Feb, 2016\n-------------------------\nChanges from beta:\n* Fixed #87\n\ndlib 0.8.0 beta1 - 7 Feb, 2016\n------------------------------\n* dlib.audio\n  * `dlib.audio` is a new package for audio processing. Supports 8 and 16 bits per sample, arbitrary sample rate and number of channels. Includes generic sound interfaces (in-memory and streamed) and their implementations. Read more [here](https://github.com/gecko0307/dlib/wiki/dlib.audio).\n  * `dlib.audio.synth` implements some basic sound synthesizers (sine wave and white noise)\n  * `dlib.audio.io.wav` - uncompressed RIFF/WAV encoder and decoder\n* dlib.image\n  * All image filters, arithmetic operations, etc. now support manual memory management\n  * New chroma key filter based on Euclidean distance (`dlib.image.filters.chromakey.chromaKeyEuclidean`)\n  * New edge detection filter based on morphological gradient (`dlib.image.filters.edgedetect.edgeDetectGradient`)\n  * Several important bugfixes (image convolution, lanczos and bicubic resampling, wrong deallocation of empty JPEGImage)\n* dlib.core\n  * Fixed erroneous deleting uninitialized thread in `dlib.core.thread`\n* dlib.filesystem\n  * Implemented missing methods in `dlib.filesystem.stdfs.StdFileSystem`: `openForIO`, `openDir`, `createDir`, `remove`. There is a known issue with `remove`: it doesn't delete directories under Windows\n* Other improvements\n  * Added [HTML documentation generator](https://github.com/gecko0307/dlib/tree/master/gendoc).\n\ndlib 0.7.1 - 2 Dec, 2015\n------------------------\nMostly bugfix release.\n* Fixed wrong iteration of `dlib.container.dict.Trie`\n* `_allocatedMemory` in `dlib.core.memory` is now marked as `__gshared`, thus working correctly with `dlib.core.thread`\n* Fixed wrong behaviour of `nextPowerOfTwo` in `dlib.math.utils`\n* Ambiguous `rotation` functions in `dlib.math.affine` and `dlib.math.quaternion` are renamed into `rotation2D` and `rotationQuaternion`, respectively\n* Added `flatten` method for matrices.\n\ndlib 0.7.0 - 2 Oct, 2015\n------------------------\nChanges from beta:\n* Fixed 64-bit issues\n* dlib now compiles with latest LDC\n* Continuous integration using Travis-CI: https://travis-ci.org/gecko0307/dlib\n\ndlib 0.7.0 beta1 - 28 Sep, 2015\n-------------------------------\n* dlib.core\n  * Added GC-free, Phobos-independent thread module - `dlib.core.thread`\n* dlib.text\n  * A new package for GC-free text processing. Includes UTF-8 decoder (`dlib.text.utf8`) and general-purpose lexical analyzer (`dlib.text.lexer`)\n* dlib.xml\n  * XML parser is now GC-free and based on `dlib.text.lexer`\n* dlib.container\n  * Added GC-free LinkedList (`dlib.container.linkedlist`), Stack (`dlib.container.stack`), Queue (`dlib.container.queue`)\n* dlib.image\n  * Fixed segfault with non-transparent indexed PNG loading\n* dlib.math\n  * Fixed error with instancing of vectors with size larger than 4\n* Other improvements\n  * Added Travis-CI support\n\ndlib 0.6.4 - 14 Sep, 2015\n-------------------------\n* Trie-based GC-free dictionary class (`std.container.dict`)\n* Several performance optimizations in `dlib.math`: vector element access and multiplication for 3x3 and 4x4 matrices are now faster\n* Fixed some 64-bit issues.\n\ndlib 0.6.3 - 17 Aug, 2015\n-------------------------\n* Fixed `dlib.filesystem.stdfs` compilation under 64-bit systems\n* Fixed PNG exporter bug with encoding non-compressible images\n* Added basic drawing functions (`dlib.image.render.shapes`)\n\ndlib 0.6.2 - 20 Jul, 2015\n-------------------------\nBugfix release.\n* Removed coordinates clamping on pixel write in dlib.image\n* Fixed bug with PNG vertical flipping\n\ndlib 0.6.1 - 6 Jul, 2015\n------------------------\n* Added memory profiler\n* Fixed `dlib.math.sse` compilation on 64-bit systems\n\ndlib 0.6.0 - 24 Jun, 2015\n-------------------------\n* dlib.core\n  * Got rid of ManuallyAllocatable interface in manual memory management for classes. Added support for deleting via interface or parent class. Deleting can be abstractized with Freeable interface\n* dlib.filesystem\n  * Added GC-free implementations for FileSystem and file streams\n* dlib.image\n  * dlib.image.unmanaged provides generalized GC-free Image class with corresponding factory function\n  * JPEG decoder had been greatly improved, added more subsampling modes support, COM and APPn markers detection. Decoder now understands virtually any imaginable baseline JPEGs, including those from digital cameras\n* dlib.math\n  * New module dlib.math.combinatorics with factorial, hyperfactorial, permutation, combinations, lucas number and other functions\n  * dlib.math.sse brings x86 SSE-based optimizations for some commonly used vector and matrix operations, namely, 4-vector arythmetics, dot and cross product, 4x4 matrix multiplication.\n* dlib.container\n  * DynamicArray now supports indexing (as a syntactic sugar).\n\ndlib 0.5.3 - 5 May, 2015\n-----------------------\n* Added Protobuf-style varint implementation (dlib.coding.varint)\n* Streams are now ManuallyAllocatable\n* Triangle struct in dlib.geometry.triangle now has tangent vectors\n* Fixed unittest build failure (#59)\n\ndlib 0.5.2 - 25 Feb, 2015\n-------------------------\n* Automated vector type conversion (#57), modulo operator for vectors (#58)\n* Fixed warning in dlib.image.io.bmp (#56)\n\ndlib 0.5.1 - 21 Feb, 2015\n-------------------------\nSmall bugfix release:\n* Fixed wrong module name in dlib.geometry.frustum\n* Updated license information\n\ndlib 0.5.0 - 20 Feb, 2015\n-------------------------\n* dlib.core\n  * Added manual memory management support. dlib.core.memory provide memory allocators based on standard C malloc/free. They can allocate arrays, classes and structs\n  * Added prototype-based OOP system for structs (dlib.core.oop) with support for multiple inheritance and parametric polymorphism\n* dlib.image\n  * Image loaders are now GC-free\n  * dlib.image.io.zlib and dlib.image.io.huffman modules are moved to new package dlib.coding. dlib.image.io.bitio moved to dlib.core.\n  * Image allocation is based on a factory interface that abstracts over GC or MMM\n  * Improved support for indexed PNGs - added alpha channel support\n* dlib.container\n  * Added GC-free dynamic array implementation (dlib.container.array)\n  * BST and AArray now use manual memory management\n* dlib.math\n  * Quaternion is now based on and interchangeable with Vector via incapsulation\n  * Dual quaternion support (dlib.math.dualquaternion)\n  * Fixed incorrect dual number `pow` implementation\n* dlib.geometry\n  * Breaking change: Frustum plane normals are now pointing outside frustum. Also Frustum-AABB intersection API is changed\n  * Fixed bugs in AABB and Plane\n\ndlib 0.4.1 - 30 Dec, 2014\n-------------------------\n* dlib.image\n  * Baseline JPEG decoder (dlib.image.io.jpeg)\n* dlib.math\n  * New matrix printer with proper alignment (a la Matlab)\n\ndlib 0.4.0 - 27 Oct, 2014\n-------------------------\n* dlib.filesystem\n  * Platform-specific modules are now grouped by corresponding packages (dlib.filesystem.windows, dlib.filesystem.posix)\n  * `findFiles` is now a free function and can be used with any `ReadOnlyFileSystem`\n* dlib.math\n  * Implemented LU decomposition for matrices (dlib.math.decomposition)\n  * `dlib.math.linear` is now `dlib.math.linsolve`. Added `solveLU`, a new LU-based direct solver\n  * Matrix inversion now uses LU decomposition by default (4x performance boost compared to old analytic method). 4x4 affine matrices use an optimized inversion, which is about 6 times faster\n  * `dlib.math.utils` now uses `clamp` from latest Phobos if available\n  * Removed deprecated functionality\n* dlib.core:\n  * Moved container modules (bst, linkedlist, etc) from dlib.core to separate package dlib.container. Removed useless dlib.core.method\n* Overall: improved compatibility with DMD 2.067.\n\ndlib 0.3.3 - 31 Jul, 2014\n-------------------------\nMainly bugfix release. Changes:\n* Fixed compilation with DMD 2.066\n* Added dlib.geometry.frustum\n* Improved dlib.math.quaternion\n\ndlib 0.3.2 - 11 Jun, 2014\n-------------------------\nBugfix release. The main improvement is fixed compilation with some versions of LDC.\n\ndlib 0.3.1 - 13 May, 2014\n-------------------------\nBugfix release. Changes:\n* Improved dlib.image, added interpolated pixel reading\n* Added matrix addition and subtraction, tensorProduct now works with any matrix sizes\n* Addressed many bugs in dlib.image and dlib.math.\n\ndlib 0.3.0 - 25 Mar, 2014\n-------------------------\n* dlib.core\n  * Added simple yet robust I/O streams (dlib.core.stream), which are completely Phobos-independent\n* dlib.filesystem\n  * Abstract FS interface and it's implementations for Windows and POSIX filesystems\n* dlib.image\n  * Breaking change: all pixel I/O is now floating-point (via `Color4f`). This gives an opportunity to define image classes of arbitrary floating-point pixel formats and enables straightforward HDRI: sample 32-bit implementation provided in dlib.image.hdri\n  * Pixel iteration now can be done with `row` and `col` ranges\n  * Parallel filtering is now easy with dlib.image.parallel. You can add multithreading to your existing filter code with just a few changes\n  * Added support for TGA and BMP formats (only loading for now)\n  * All image format I/O is now stream-based\n* dlib.math\n  * Breaking change: matrices in dlib.math.matrix are now column-major\n  * Imporved constness support in dlib.math.vector, as well as added unittest to the module. Using new string constructor, vectors now can be parsed from strings (e.g., `\"[0, 1, 2]\"`)\n* Overall improvements & bugfixes\n  * Much saner DUB support, addressed some serious problems with building, added configuration for pre-compiling as a static library\n\ndlib 0.2.4 - 12 Dec, 2013\n-------------------------\nBugfix release + added support for DMD 2.064 package modules.\n\ndlib 0.2.3 - 6 Dec, 2013\n------------------------\nBugfix release. Fixed issues with compiling on 64-bit systems.\n\ndlib 0.2.1 - 20 Nov, 2013\n-------------------------\nBugfix release.\n\ndlib 0.2.0 - 11 Oct, 2013\n-------------------------\n* Added XML parser (alpha quality);\n* Massive refactoring of the matrix implementation. All matrix types (Matrix2x2f, Matrix3x3f, Matrix4x4f) are now specializations of generic Matrix!(T,N) struct in dlib.math.matrix;\n* Updated dlib.math.dual. Vectors of dual numbers can now be created;\n* Added support for Hermite curves (dlib.geometry.hermite).\n\ndlib 0.1.2 - 18 Jul, 2013\n-------------------------\n* Renamed ColorRGBA and ColorRGBAf into Color4 and Color4f;\n* Added support for image convolution. There are several built-in kernels (Identity, BoxBlur, GaussianBlur, Sharpen, Emboss, EdgeEmboss, EdgeDetect, Laplace);\n* Added support for HSV color space;\n* Added Chroma Keying and Color Pass filters.\n\ndlib 0.1.1 - 13 Jul, 2013\n-------------------------\nBugfix release.\n\ndlib 0.1.0 - 13 Jul, 2013\n-------------------------\nInitial release.\n\n13 Jul, 2013\n------------\nProject moved to GitHub.\n\n2012-2013\n---------\nEarly development on code.google.com.\n\n28 Sep, 2012\n------------\nStart as a public project.\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\nThis project welcomes contributions in any form and encourages open discussion and exchange of views on its features, architecture, and implementation details. To maintain a proper culture of communication, this Code of Conduct has been formulated. It applies to the project's issue tracker, chat, and possibly other communication channels.\n\n1. We do not tolerate obscene language, insulting, rude and/or disparaging messages, the use of sexualized, violent and otherwise offensive speech and imagery.\n2. Any criticism should be constructive and reasonable. Mere personal discontent without any objective reasoning is not enough to make critical statements and influence the development of dlib.\n3. This project stays away from non-technological issues and topics. We welcome everyone, regardless of age, gender identity, religion, ethnicity, citizenship, or cultural background, and our community do not conduct any specific ideology. Sites, repositories, communication channels and other resources associated with dlib should not be used as a platform for propaganda.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing Guidelines\n\n#### Bug reporting \n\nOpen an issue at [GitHub issue tracker](https://github.com/gecko0307/dlib/issues). Before doing that, ensure the bug was not already reported or fixed in `master` branch. Describe a problem and, if necessary, provide minimal code needed to reproduce it.\n\nNote that macOS compatibility issues are not considered bugs. dlib intentionally doesn't support macOS anymore. Read more [here](https://github.com/gecko0307/dlib/wiki/Why-doesn't-dlib-support-macOS).\n\nBugs that have not been reproduced or discussed for 6 months or longer are marked as `wontfix` and closed.\n\nWe also use several other labels:\n* Breaking change. Self-descriptive: an improvement that breaks backward compatibility\n* Bug. A bug that should be fixed without API change\n* Enhancement. A non-breaking improvement of existing functionality (optimization or a new feature)\n* Missing. Appears when existing functionality is removed due to regressions and needs to be rewritten, or when some implementation is not complete\n* New functionality. Self-descriptive: a new functionality request.\n\n#### Bug fixing \n\nOpen a new GitHub pull request with your patch. Provide a description of the problem and solution. Follow our [code style](#code-style-and-standards). Please, try to avoid solutions that break library API and semantics - such changes should be made very carefully. If the problem can't be solved without breaking changes, explicitly state that in the description.\n\n#### Implementing new features \n\nBefore writing a new module, familiarize yourself with [project philosophy](https://github.com/gecko0307/dlib/wiki/Rationale) and [best practices](https://github.com/gecko0307/dlib/wiki/Best-Practices). Despite being a general-purpose library, dlib is not a place for rarely used or too domain-specific code. In most cases it's better to start a new library instead of pushing new modules to dlib. Only in case you find yourself constantly reusing some generic functionality in different projects it may be reasonable to propose such code to dlib. It may be a data structure, sorting algorithm, data compression method, image file decoder, communication protocol, or anything of that sort.\n\nNew code should at least:\n* work under Windows and POSIX systems and provide platform-agnostic API\n* support x86 and x86_64 targets\n* not rely on third party libraries other than system API\n* follow dlib's [code style](#code-style-and-standards)\n* use transparent dynamic memory allocations. Ideally the code should not allocate at all or rely on user for that. If internal dynamic allocations can't be avoided, they should be done with `dlib.core.memory` or `dlib.memory`. Direct garbage collector usage is discouraged\n* follow [dlib's best practices](https://github.com/gecko0307/dlib/wiki/Best-Practices), making use of ownership, containers, streams, exceptionless error handling and filesystem abstraction\n* not violate copyright/licensing. When adapting third-party code, make sure that it is compatible with [Boost Software License 1.0](https://www.boost.org/LICENSE_1_0.txt).\n\n#### Code style and standards\n\ndlib follows [D style](https://dlang.org/dstyle.html). Essential rules are the following:\n* Use spaces instead of tabs. Each indentation level is 4 spaces\n* Opening curly bracket should be on a new line (Allman style)\n* Functions and variables should be in `camelCase`\n* Types, constants and enums should be in `PascalCase`\n* Module names should be in lowercase.\n\nAll modules in dlib should belong to a package (`dlib.core`, `dlib.math`, `dlib.image`, etc.). Keep package and module names short and informative. Modules with related functionality can be combined to subpackages (such as `dlib.image.io`). Provide a package import module (`package.d` with public imports) for each subpackage.\n\nEach D module should start with a Boost license block prepended with a copyright notice:\n```d\n/*\nCopyright (c) 2026 <author's name here>\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n```\n\n#### Documenting\n\nIt is not strictly necessary to document code, but if you do, use [ddoc syntax](https://dlang.org/spec/ddoc.html). Each documented module should be accompanied by a description block (if you don't do that, documentation generators may ignore the module):\n```d\n/**\n * <One-line description of the module>\n *\n * Description:\n * <more in-depth information (optional)>\n *\n * Copyright: Your Name 2026.\n * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Your Name\n */\nmodule dlib.something.something;\n```\n\n#### Testing\n\nIt is advisable to write unit tests for new code, if they can be written. Sometimes functionality needs particular external environment or special conditions to run; in these cases tests are not required.\n\nIt is recommended to add one `unittest` block per function, method, or class. Test block should be formatted in the following way:\n\n```d\n///\nunittest\n{\n}\n```\n"
  },
  {
    "path": "COPYING.md",
    "content": "Boost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n"
  },
  {
    "path": "Doxyfile",
    "content": "DOXYFILE_ENCODING      = UTF-8\nPROJECT_NAME           = \"dlib\"\nPROJECT_NUMBER         = 1.1\nPROJECT_BRIEF          = \"Allocators, I/O streams, math, geometry, image and audio processing for D\"\nPROJECT_LOGO           = \"logo/dlib-logo-doxygen.png\"\nOUTPUT_DIRECTORY       = \"docs-doxygen\"\nCREATE_SUBDIRS         = YES\nCREATE_SUBDIRS_LEVEL   = 8\nALLOW_UNICODE_NAMES    = NO\nOUTPUT_LANGUAGE        = English\nGENERATE_HTML          = YES\nHTML_OUTPUT            = html\nHTML_FILE_EXTENSION    = .html\nGENERATE_HTMLHELP      = YES\nCHM_FILE               = \"documentation.chm\"\nINPUT                  = \"dlib\"\nRECURSIVE              = YES\n"
  },
  {
    "path": "README.md",
    "content": "<img align=\"left\" alt=\"dlib logo\" src=\"https://github.com/gecko0307/dlib/raw/master/logo/dlib-logo.png\" height=\"66\" />\n\ndlib is a high-level general purpose library written in [D language](https://dlang.org) for game and graphics developers. It provides basic building blocks for writing real-time and multimedia applications: containers, data streams, linear algebra and image decoders.\n\ndlib has no external dependencies aside D's standard library. dlib is created and maintained by [Timur Gafarov](https://github.com/gecko0307).\n\n[![GitHub Actions CI Status](https://github.com/gecko0307/dlib/workflows/CI/badge.svg)](https://github.com/gecko0307/dlib/actions?query=workflow%3ACI)\n[![DUB Package](https://img.shields.io/dub/v/dlib.svg)](https://code.dlang.org/packages/dlib)\n[![DUB Downloads](https://img.shields.io/dub/dm/dlib.svg)](https://code.dlang.org/packages/dlib)\n[![License](http://img.shields.io/badge/license-boost-blue.svg)](http://www.boost.org/LICENSE_1_0.txt)\n[![Codecov](https://codecov.io/gh/gecko0307/dlib/branch/master/graph/badge.svg)](https://codecov.io/gh/gecko0307/dlib)\n[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gecko0307/dlib?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n\nIf you like dlib, please support its development on [Patreon](https://www.patreon.com/gecko0307) or [Liberapay](https://liberapay.com/gecko0307). You can also make one-time donation via [NOWPayments](https://nowpayments.io/donation/gecko0307). I appreciate any support. Thanks in advance!\n\nWhat's inside\n-------------\nCurrently dlib consists of the following packages:\n* [dlib.core](https://gecko0307.github.io/dlib/docs/dlib/core.html) - basic functionality used by other modules (memory management, streams, threads, etc.)\n* [dlib.container](https://gecko0307.github.io/dlib/docs/dlib/container.html) - generic data structures (GC-free dynamic and associative arrays and more)\n* [dlib.filesystem](https://gecko0307.github.io/dlib/docs/dlib/filesystem.html) - abstract FS interface and its implementations for Windows and POSIX filesystems\n* [dlib.math](https://gecko0307.github.io/dlib/docs/dlib/math.html) - linear algebra and numerical analysis (vectors, matrices, quaternions, linear system solvers, interpolation functions, etc.)\n* [dlib.geometry](https://gecko0307.github.io/dlib/docs/dlib/geometry.html) - computational geometry (ray casting, primitives, intersection, etc.)\n* [dlib.image](https://gecko0307.github.io/dlib/docs/dlib/image.html) - image processing (8-bit, 16-bit and 32-bit floating point channels, common filters and convolution kernels, resizing, FFT, HDRI, animation, graphics formats I/O: JPEG, PNG/APNG, BMP, TGA, HDR)\n* [dlib.audio](https://gecko0307.github.io/dlib/docs/dlib/audio.html) - sound processing (8 and 16 bits per sample, synthesizers, WAV export and import)\n* [dlib.network](https://gecko0307.github.io/dlib/docs/dlib/network.html) - networking and web functionality\n* [dlib.memory](https://gecko0307.github.io/dlib/docs/dlib/memory.html) - memory allocators\n* [dlib.text](https://gecko0307.github.io/dlib/docs/dlib/text.html) - text processing, GC-free strings, Unicode decoding and encoding\n* [dlib.serialization](https://gecko0307.github.io/dlib/docs/dlib/serialization.html) - data serialization (XML and JSON parsers)\n* [dlib.random](https://gecko0307.github.io/dlib/docs/dlib/random.html) - random number generation\n* [dlib.coding](https://gecko0307.github.io/dlib/docs/dlib/coding.html) - various data compression and coding algorithms\n* [dlib.concurrency](https://gecko0307.github.io/dlib/docs/dlib/concurrency.html) - a thread pool.\n\nSupported Compilers\n-------------------\ndlib is automatically tested for compatibility with latest releases of DMD and LDC. Older releases formally are not supported, but in practice usually are, to some extent. There's no guaranteed support for GDC and other D compilers.\n\nDocumentation\n-------------\nHTML documentation can be generated from source code using ddox (run `dub build -b ddox`) or Doxygen (run `doxygen`). Be aware that documentation is currently incomplete.\n\n* [Autogenerated documentation](https://gecko0307.github.io/dlib/docs/dlib.html)\n* [Wiki](https://github.com/gecko0307/dlib/wiki)\n* [Rationale](https://github.com/gecko0307/dlib/wiki/Rationale)\n* [FAQ](https://github.com/gecko0307/dlib/wiki/FAQ)\n* [Best Practices](https://github.com/gecko0307/dlib/wiki/Best-Practices)\n* [Contributing Guidelines](https://github.com/gecko0307/dlib/blob/master/CONTRIBUTING.md)\n* [Gitter chat room](https://gitter.im/gecko0307/dlib)\n\nLicense\n-------\nCopyright (c) 2011-2026 Timur Gafarov, Martin Cejp, Andrey Penechko, Vadim Lopatin, Nick Papanastasiou, Oleg Baharev, Roman Chistokhodov, Eugene Wissner, Roman Vlasov, Basile Burg, Valeriy Fedotov, Ferhat Kurtulmuş, João Lourenço, Ate Eskola, Aaron Nédélec, Alexander Perfilyev. Distributed under the Boost Software License, Version 1.0 (see accompanying file COPYING or at https://www.boost.org/LICENSE_1_0.txt).\n\nSponsors\n--------\nJan Jurzitza (WebFreak), Daniel Laburthe, Rafał Ziemniewski, Kumar Sookram, Aleksandr Kovalev, Robert Georges, Rais Safiullin (SARFEX), Benas Cernevicius, Koichi Takio, Konstantin Menshikov.\n\nUsers\n-----\n* [Dagon](https://github.com/gecko0307/dagon) - 3D game engine for D\n* [Electronvolt](https://github.com/gecko0307/electronvolt) - work-in-progress first person puzzle game\n* [DagoBan](https://github.com/Timu5/dagoban) - Sokoban clone\n* [DlangUI](https://github.com/buggins/dlangui) - native UI toolkit for D\n* [rengfx](https://github.com/xdrie/rengfx) - game engine based on raylib\n* [Voxelman](https://github.com/MrSmith33/voxelman) - voxel-based game engine\n* [RIP](https://github.com/LightHouseSoftware/rip) - image processing and analysis library by LightHouse Software\n* [GeneticAlgorithm](https://github.com/Hnatekmar/GeneticAlgorithm) - genetic algorithms library\n* [Orb](https://github.com/claudemr/orb) - a game/engine with procedural content\n* [Leptbag](https://github.com/thotgamma/LeptbagCpp) - physics simulator by Gamma-Lab. Written in C++, but supports D plugins\n* [aoynthesizer](https://github.com/AODQ/aoynthesizer) - sound synthesizer based on Lisp-like scripting language\n* [D-VXLMapPreview](https://github.com/rakiru/D-VXLMapPreview) - isometric preview generator for Ace of Spades and Iceball maps\n* [SMSFontConverter](https://github.com/Doom2fan/SMSFontConverter) - a program for generating Sega Master System fonts\n* [sacengine](https://github.com/tg-2/sacengine/) - engine reimplementation for the game Sacrifice\n* [Shaders Playground](https://github.com/AntonC9018/shader_playground) - a sandbox project for experimenting with OpenGL shaders\n* [wgpu-dlang](https://github.com/gecko0307/wgpu-dlang) - WebGPU usage example.\n"
  },
  {
    "path": "dlib/audio/io/package.d",
    "content": "/*\nCopyright (c) 2022-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Copyright: Timur Gafarov 2022-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.audio.io;\n\nimport std.path: extension;\nimport dlib.audio.sound;\n\npublic\n{\n    import dlib.audio.io.wav;\n}\n\nclass SoundLoadException: Exception\n{\n    this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)\n    {\n        super(msg, file, line, next);\n    }\n}\n\n/**\n * Saves a sound to file, selects encoder by filename extension\n */\nvoid saveSound(GenericSound snd, string filename)\n{\n    switch(filename.extension)\n    {\n        case \".wav\", \".WAV\":\n            snd.saveWAV(filename);\n            break;\n        default:\n            assert(0, \"Sound I/O error: unsupported sound format or illegal extension\");\n    }\n}\n\n/**\n * Loads sound from a file, selects decoder by filename extension\n */\nGenericSound loadSound(string filename)\n{\n    switch(filename.extension)\n    {\n        case \".wav\", \".WAV\":\n            return loadWAV(filename);\n        default:\n            assert(0, \"Sound I/O error: unsupported sound format or illegal extension\");\n    }\n}\n"
  },
  {
    "path": "dlib/audio/io/wav.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Uncompressed RIFF/WAV encoder and decoder\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.audio.io.wav;\r\n\r\n//version = WAVDebug; // Uncomment to see debug messages\r\n\r\nversion(WAVDebug)\r\n{\r\n    import std.stdio;\r\n}\r\nimport dlib.core.memory;\r\nimport dlib.core.stream;\r\nimport dlib.filesystem.local;\r\nimport dlib.audio.sample;\r\nimport dlib.audio.sound;\r\n\r\n/**\r\n * Simple RIFF/WAV decoder. Decodes WAV from stream using provided sound factory\r\n */\r\nGenericSound loadWAV(InputStream istrm, GenericSoundFactory gsf)\r\n{\r\n    char[4] magic;\r\n    istrm.fillArray(magic);\r\n    assert(magic == \"RIFF\");\r\n\r\n    int chunkSize;\r\n    istrm.readLE(&chunkSize);\r\n    version(WAVDebug)\r\n    {\r\n        writeln(chunkSize);\r\n    }\r\n\r\n    char[4] format;\r\n    istrm.fillArray(format);\r\n    version(WAVDebug)\r\n    {\r\n        writeln(format);\r\n    }\r\n    assert(format == \"WAVE\");\r\n\r\n    char[4] fmtSubchunkID; // fmt\r\n    istrm.fillArray(fmtSubchunkID);\r\n\r\n    int fmtSubchunkSize;\r\n    istrm.readLE(&fmtSubchunkSize);\r\n\r\n    short audioFormat;\r\n    short numChannels;\r\n    int sampleRate;\r\n    int byteRate;\r\n    short blockAlign;\r\n    short bitsPerSample;\r\n\r\n    istrm.readLE(&audioFormat);\r\n    istrm.readLE(&numChannels);\r\n    istrm.readLE(&sampleRate);\r\n    istrm.readLE(&byteRate);\r\n    istrm.readLE(&blockAlign);\r\n    istrm.readLE(&bitsPerSample);\r\n\r\n    version(WAVDebug)\r\n    {\r\n        writefln(\"Format: %s\", audioFormat);\r\n        writefln(\"Number of channels: %s\", numChannels);\r\n        writefln(\"Sample rate: %s Hz\", sampleRate);\r\n        writefln(\"Byte rate: %s\", byteRate);\r\n        writefln(\"Block align: %s\", blockAlign);\r\n        writefln(\"Bits per sample: %s\", bitsPerSample);\r\n    }\r\n\r\n    char[4] dataSubchunkID; // data\r\n    istrm.fillArray(dataSubchunkID);\r\n\r\n    int dataSubchunkSize;\r\n    istrm.readLE(&dataSubchunkSize);\r\n    version(WAVDebug)\r\n    {\r\n        writeln(dataSubchunkSize);\r\n    }\r\n    int numSamples = dataSubchunkSize / (numChannels * bitsPerSample/8);\r\n    version(WAVDebug)\r\n    {\r\n        writefln(\"Number of samples: %s\", numSamples);\r\n        writefln(\"Duration: %s\", (cast(float)numSamples) / sampleRate);\r\n    }\r\n\r\n    SampleFormat f;\r\n    if (bitsPerSample == 8)\r\n    {\r\n        f = SampleFormat.U8;\r\n    }\r\n    else if (bitsPerSample == 16)\r\n    {\r\n        f = SampleFormat.S16;\r\n    }\r\n\r\n    GenericSound gs;\r\n    gs = gsf.createSound(\r\n        dataSubchunkSize,\r\n        numSamples,\r\n        (cast(double)numSamples) / sampleRate,\r\n        numChannels,\r\n        sampleRate,\r\n        bitsPerSample,\r\n        f\r\n    );\r\n    istrm.fillArray(gs.data);\r\n\r\n    return gs;\r\n}\r\n\r\n/**\r\n * Decodes WAV from stream using default sound factory\r\n */\r\nGenericSound loadWAV(InputStream istrm)\r\n{\r\n    return loadWAV(istrm, defaultGenericSoundFactory);\r\n}\r\n\r\n/**\r\n * Decodes WAV from file\r\n */\r\nGenericSound loadWAV(string filename)\r\n{\r\n    auto istrm = openForInput(filename);\r\n    ubyte[] data = New!(ubyte[])(cast(size_t)istrm.size);\r\n    istrm.fillArray(data);\r\n    ArrayStream arrStrm = New!ArrayStream(data);\r\n    auto snd = loadWAV(arrStrm);\r\n    Delete(arrStrm);\r\n    Delete(data);\r\n    istrm.close();\r\n    return snd;\r\n}\r\n\r\n/**\r\n * Simple RIFF/WAV encoder. Encodes WAV to stream\r\n */\r\nvoid saveWAV(Sound snd, OutputStream ostrm)\r\n{\r\n    string magic = \"RIFF\";\r\n    ostrm.writeBytes(magic.ptr, 4);\r\n\r\n    int chunkSize = 36 + cast(int)snd.data.length; // total file size - 8\r\n    ostrm.writeLE(chunkSize);\r\n\r\n    string format = \"WAVE\";\r\n    ostrm.writeBytes(format.ptr, 4);\r\n\r\n    string fmt = \"fmt \";\r\n    ostrm.writeBytes(fmt.ptr, 4);\r\n\r\n    int fmtSubchunkSize = 16;\r\n    ostrm.writeLE(fmtSubchunkSize);\r\n\r\n    short audioFormat = 1;\r\n    short numChannels = cast(short)snd.channels;\r\n    assert(numChannels == 1 || numChannels == 2);\r\n    int sampleRate = snd.sampleRate; // samples per second\r\n    int byteRate = snd.sampleRate * snd.sampleSize; // bytes per second\r\n    short blockAlign = cast(short)snd.sampleSize; // bytes per sample\r\n    short bitsPerSample;\r\n    if (snd.sampleFormat == SampleFormat.U8)\r\n        bitsPerSample = 8;\r\n    else if (snd.sampleFormat == SampleFormat.S16)\r\n        bitsPerSample = 16;\r\n    else\r\n    {\r\n        assert(0, \"Illegal sample format to use with RIFF/WAV\");\r\n    }\r\n\r\n    ostrm.writeLE(audioFormat);\r\n    ostrm.writeLE(numChannels);\r\n    ostrm.writeLE(sampleRate);\r\n    ostrm.writeLE(byteRate);\r\n    ostrm.writeLE(blockAlign);\r\n    ostrm.writeLE(bitsPerSample);\r\n\r\n    string dataID = \"data\";\r\n    ostrm.writeBytes(dataID.ptr, 4);\r\n\r\n    int dataSubchunkSize = cast(int)snd.data.length;\r\n    ostrm.writeLE(dataSubchunkSize);\r\n    ostrm.writeArray(snd.data);\r\n}\r\n\r\n/**\r\n * Encodes WAV to file\r\n */\r\nvoid saveWAV(Sound snd, string filename)\r\n{\r\n    auto ostrm = openForOutput(filename);\r\n    saveWAV(snd, ostrm);\r\n    ostrm.close();\r\n}\r\n"
  },
  {
    "path": "dlib/audio/package.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * This package provides basic tools for sound processing. It currently supports \r\n * bit depths 8 and 16 (signed and unsigned), as well as arbitrary sample rate and \r\n * number of channels. Design principles of dlib.audio are closely akin to dlib.image.\r\n *\r\n * Copyright: Timur Gafarov 2016-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.audio;\r\n\r\npublic\r\n{\r\n    import dlib.audio.sample;\r\n    import dlib.audio.sound;\r\n    import dlib.audio.synth;\r\n    import dlib.audio.unmanaged;\r\n    import dlib.audio.io;\r\n    import dlib.audio.io.wav;\r\n}\r\n"
  },
  {
    "path": "dlib/audio/sample.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Sample types and conversion utilities\r\n *\r\n * Copyright: Timur Gafarov 2016-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.audio.sample;\r\n\r\n/**\r\n * dlib.audio defines four integer sample formats: signed 8-bit, signed 16-bit,\r\n * unsigned 8-bit, unsigned 16-bit.\r\n * They are for storage only. All sound processing and sample I/O\r\n * should be done in floating point numbers. Floating point sample is signed and\r\n * ranges from -1.0f to 1.0f.\r\n */\r\nenum SampleFormat\r\n{\r\n    /// signed 8-bit\r\n    S8,\r\n    /// signed 16-bit\r\n    S16,\r\n    /// unsigned 8-bit\r\n    U8,\r\n    /// unsigned 16-bit\r\n    U16\r\n}\r\n\r\n/**\r\n * Convert integer sample to floating point sample\r\n */\r\nfloat toFloatSample(ubyte* ptr, SampleFormat format)\r\n{\r\n    float res;\r\n    switch (format)\r\n    {\r\n        case SampleFormat.S8:\r\n            res = *cast(byte*)ptr;\r\n            res /= byte.max;\r\n            break;\r\n        case SampleFormat.S16:\r\n            res = *cast(short*)ptr;\r\n            res /= short.max;\r\n            break;\r\n        case SampleFormat.U8:\r\n            res = *ptr;\r\n            res /= ubyte.max;\r\n            res = res * 2.0f - 1.0f; // normalize to range -1..1\r\n            break;\r\n        case SampleFormat.U16:\r\n            res = *cast(ushort*)ptr;\r\n            res /= ushort.max;\r\n            res = res * 2.0f - 1.0f; // normalize to range -1..1\r\n            break;\r\n        default:\r\n            break;\r\n    }\r\n    return res;\r\n}\r\n\r\n/**\r\n * Convert floating point sample to integer sample\r\n */\r\nvoid fromFloatSample(ubyte* ptr, SampleFormat format, float s)\r\n{\r\n    switch (format)\r\n    {\r\n        case SampleFormat.S8:\r\n            *cast(byte*)ptr = cast(byte)(s * byte.max);\r\n            break;\r\n        case SampleFormat.S16:\r\n            *cast(short*)ptr = cast(short)(s * short.max);\r\n            break;\r\n        case SampleFormat.U8:\r\n            *cast(ubyte*)ptr = cast(ubyte)((s + 1.0f) * 0.5f * ubyte.max);\r\n            break;\r\n        case SampleFormat.U16:\r\n            *cast(ushort*)ptr = cast(ushort)((s + 1.0f) * 0.5f * ushort.max);\r\n            break;\r\n        default:\r\n            break;\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/audio/sound.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Generic sound interfaces and their implementations\r\n *\r\n * Copyright: Timur Gafarov 2016-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.audio.sound;\r\n\r\nimport std.math;\r\nimport dlib.audio.sample;\r\n\r\n/**\r\n * Generalized sound stream class.\r\n * This can be used to implement any type of sound,\r\n * including compressed audio streams.\r\n */\r\nabstract class StreamedSound\r\n{\r\n    /// Number of channels per sample\r\n    @property uint channels();\r\n\r\n    /// Number of samples per second, in Hz\r\n    @property uint sampleRate();\r\n\r\n    /// Number of bits in each sample channel\r\n    @property uint bitDepth();\r\n\r\n    /// Number of bytes in each sample (bitDepth / 8 * channels)\r\n    @property uint sampleSize()\r\n    {\r\n        return (bitDepth / 8) * channels;\r\n    }\r\n\r\n    /// Sample format\r\n    @property SampleFormat sampleFormat();\r\n\r\n    /// Fill in the buffer with next portion of sound data.\r\n    /// Function returns actual number of bytes written.\r\n    /// At the end of a stream, zero is returned.\r\n    ulong stream(ubyte[] buffer);\r\n\r\n    /// Return to the start of a stream\r\n    void reset();\r\n}\r\n\r\n/**\r\n * Sound that can be seeked\r\n */\r\nabstract class SeekableSound: StreamedSound\r\n{\r\n    /// Number of samples\r\n    @property ulong size();\r\n\r\n    /// Duration, in seconds\r\n    @property double duration();\r\n\r\n    float opIndex(uint chan, ulong pos);\r\n    float opIndexAssign(float sample, uint chan, ulong pos);\r\n}\r\n\r\n/**\r\n * Sound that is fully kept in memory\r\n */\r\nabstract class Sound: SeekableSound\r\n{\r\n    /// Raw byte data\r\n    @property ref ubyte[] data();\r\n\r\n    /// Make exact copy of the sound\r\n    @property Sound dup();\r\n\r\n    /// Make empty sound of the same format\r\n    Sound createSameFormat(uint ch, double dur);\r\n}\r\n\r\n/**\r\n * Generic Sound implementation\r\n */\r\nclass GenericSound: Sound\r\n{\r\n    protected:\r\n    ubyte[] _data;\r\n    ulong _size;\r\n    double _duration;\r\n    uint _channels;\r\n    uint _sampleRate;\r\n    uint _bitDepth;\r\n    SampleFormat _format;\r\n\r\n    public:\r\n    this(Sound ras)\r\n    {\r\n        auto rasData = ras.data;\r\n        allocateData(rasData.length);\r\n        for(size_t i = 0; i < _data.length; i++)\r\n            _data[i] = rasData[i];\r\n        _size = ras.size;\r\n        _duration = ras.duration;\r\n        _channels = ras.channels;\r\n        _sampleRate = ras.sampleRate;\r\n        _bitDepth = ras.bitDepth;\r\n        _format = ras.sampleFormat;\r\n    }\r\n\r\n    this(double dur,\r\n         uint freq,\r\n         uint numChannels,\r\n         SampleFormat f)\r\n    {\r\n        _duration = dur;\r\n        _sampleRate = freq;\r\n        _channels = numChannels;\r\n        _size = cast(ulong)ceil(dur * freq);\r\n        _format = f;\r\n        if (f == SampleFormat.U8 || f == SampleFormat.S8)\r\n            _bitDepth = 8;\r\n        else if (f == SampleFormat.U16 || f == SampleFormat.S16)\r\n            _bitDepth = 16;\r\n        size_t dataSize = cast(size_t)(_size * (numChannels * (_bitDepth / 8)));\r\n        allocateData(dataSize);\r\n    }\r\n\r\n    this(size_t dataSize,\r\n         ulong numSamples,\r\n         double dur,\r\n         uint numChannels,\r\n         uint freq,\r\n         uint bitdepth,\r\n         SampleFormat f)\r\n    {\r\n        allocateData(dataSize);\r\n        _size = numSamples;\r\n        _duration = dur;\r\n        _channels = numChannels;\r\n        _sampleRate = freq;\r\n        _bitDepth = bitdepth;\r\n        _format = f;\r\n    }\r\n\r\n    override @property ulong size()\r\n    {\r\n        return _size;\r\n    }\r\n\r\n    override @property double duration()\r\n    {\r\n        return _duration;\r\n    }\r\n\r\n    override @property uint channels()\r\n    {\r\n        return _channels;\r\n    }\r\n\r\n    override @property uint sampleRate()\r\n    {\r\n        return _sampleRate;\r\n    }\r\n\r\n    override @property uint bitDepth()\r\n    {\r\n        return _bitDepth;\r\n    }\r\n\r\n    override @property SampleFormat sampleFormat()\r\n    {\r\n        return _format;\r\n    }\r\n\r\n    override @property ref ubyte[] data()\r\n    {\r\n        return _data;\r\n    }\r\n\r\n    protected:\r\n    size_t position; // sample position\r\n\r\n    protected void allocateData(size_t size)\r\n    {\r\n        _data = new ubyte[size];\r\n    }\r\n\r\n    public:\r\n    override ulong stream(ubyte[] buffer)\r\n    {\r\n        size_t ssize = sampleSize();\r\n        size_t bytePos = position * ssize;\r\n        size_t sizeInBytes = cast(size_t)_size * ssize;\r\n\r\n        size_t bytesWritten = buffer.length;\r\n        if (bytePos + buffer.length >= sizeInBytes)\r\n            bytesWritten = sizeInBytes - bytePos;\r\n\r\n        for(size_t i = bytePos; i < bytePos + bytesWritten; i++)\r\n        {\r\n            buffer[i] = _data[i];\r\n        }\r\n\r\n        return bytesWritten;\r\n    }\r\n\r\n    override void reset()\r\n    {\r\n        position = 0;\r\n    }\r\n\r\n    override float opIndex(uint chan, ulong pos)\r\n    {\r\n        size_t ssize = sampleSize();\r\n        uint sampleChannelSize = _bitDepth / 8;\r\n        ubyte* samplePtr = &_data[cast(size_t)pos * ssize] + chan * sampleChannelSize;\r\n        return toFloatSample(samplePtr, _format);\r\n    }\r\n\r\n    override float opIndexAssign(float s, uint chan, ulong pos)\r\n    {\r\n        size_t ssize = sampleSize();\r\n        uint sampleChannelSize = _bitDepth / 8;\r\n        ubyte* samplePtr = &_data[cast(size_t)pos * ssize] + chan * sampleChannelSize;\r\n        fromFloatSample(samplePtr, _format, s);\r\n        return s;\r\n    }\r\n\r\n    override @property Sound dup()\r\n    {\r\n        return new GenericSound(this);\r\n    }\r\n\r\n    override Sound createSameFormat(uint ch, double dur)\r\n    {\r\n        return new GenericSound(dur, _sampleRate, ch, _format);\r\n    }\r\n}\r\n\r\n/**\r\n * GenericSound object factory\r\n */\r\ninterface GenericSoundFactory\r\n{\r\n    GenericSound createSound(\r\n        size_t dataSize,\r\n        ulong numSamples,\r\n        double dur,\r\n        uint numChannels,\r\n        uint freq,\r\n        uint bitdepth,\r\n        SampleFormat f);\r\n}\r\n\r\n/**\r\n * GenericSoundFactory implementation for Sound class\r\n */\r\nclass DefaultGenericSoundFactory: GenericSoundFactory\r\n{\r\n    GenericSound createSound(\r\n        size_t dataSize,\r\n        ulong numSamples,\r\n        double dur,\r\n        uint numChannels,\r\n        uint freq,\r\n        uint bitdepth,\r\n        SampleFormat f)\r\n    {\r\n        return new GenericSound(\r\n            dataSize,\r\n            numSamples,\r\n            dur,\r\n            numChannels,\r\n            freq,\r\n            bitdepth,\r\n            f\r\n        );\r\n    }\r\n}\r\n\r\nprivate __gshared DefaultGenericSoundFactory _defaultGenericSoundFactory;\r\n\r\n/**\r\n * GenericSoundFactory singleton\r\n */\r\nDefaultGenericSoundFactory defaultGenericSoundFactory()\r\n{\r\n    if (!_defaultGenericSoundFactory)\r\n        _defaultGenericSoundFactory = new DefaultGenericSoundFactory();\r\n    return _defaultGenericSoundFactory;\r\n}\r\n"
  },
  {
    "path": "dlib/audio/synth.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\n/**\n * Simplest waveform synthesizers\n *\n * Copyright: Timur Gafarov 2016-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\r\nmodule dlib.audio.synth;\r\n\r\nimport std.math;\r\nimport std.random;\r\nimport dlib.audio.sound;\n\n/**\r\n * An interface for a synthesizer that maps sample position to -1..1 sample value\r\n */\ninterface Synth\n{\n    /**\n     * Evaluate a sample.\n     * \n     * Params:\n     *   sound = a sound object which parameters are used to discretize a sample\n     *   position = sample index\n     *   frequency = signal frequency in Hz\n     */\n    float eval(Sound sound, ulong position, float frequency);\n}\n\n/**\r\n * Sine wave synthesizer\r\n */\nclass SineWaveSynth: Synth\n{\n    /**\n     * Evaluate a sine wave sample.\n     * \n     * Params:\n     *   sound = a sound object which parameters are used to discretize a sample\n     *   position = sample index\n     *   frequency = signal frequency in Hz\n     */\n    float eval(Sound sound, ulong position, float frequency)\n    {\n        double samplePeriod = 1.0 / cast(double)sound.sampleRate;\n        double time = position * samplePeriod;\n        return sin(2.0 * PI * frequency * time);\n    }\n}\n\n/**\r\n * Square wave synthesizer\r\n */\nclass SquareWaveSynth: Synth\n{\n    /**\n     * Evaluate square wave sample.\n     * \n     * Params:\n     *   sound = a sound object which parameters are used to discretize a sample\n     *   position = sample index\n     *   frequency = signal frequency in Hz\n     */\n    float eval(Sound sound, ulong position, float frequency)\n    {\n        double samplePeriod = 1.0 / cast(double)sound.sampleRate;\n        double phase = position * samplePeriod * frequency;\r\n        double s = 2.0 * floor(phase) - floor(2.0 * phase) + 1.0;\n        return s * 2.0 - 1.0;\n    }\n}\n\n/**\n * Sawtooth wave synthesizer\n */\nclass SawtoothWaveSynth: Synth\n{\n    /**\n     * Evaluate sawtooth wave sample.\n     * \n     * Params:\n     *   sound = a sound object which parameters are used to discretize a sample\n     *   position = sample index\n     *   frequency = signal frequency in Hz\n     */\n    float eval(Sound sound, ulong position, float frequency)\n    {\n        double samplePeriod = 1.0 / cast(double)sound.sampleRate;\n        double phase = position * samplePeriod * frequency;\n        double s = phase - floor(phase);\n        return s * 2.0 - 1.0;\n    }\n}\n\n/**\n * Triangle wave synthesizer\n */\nclass TriangleWaveSynth: Synth\n{\n    /**\n     * Evaluate triangle wave sample.\n     * \n     * Params:\n     *   sound = a sound object which parameters are used to discretize a sample\n     *   position = sample index\n     *   frequency = signal frequency in Hz\n     */\n    float eval(Sound sound, ulong position, float frequency)\n    {\n        double samplePeriod = 1.0 / cast(double)sound.sampleRate;\n        double phase = position * samplePeriod * frequency;\n        double s = abs(1.0 - fmod(phase, 2.0));\n        return s * 2.0 - 1.0;\n    }\n}\n\n/**\r\n * Frequency modulation synthesizer\r\n */\nclass FMSynth: Synth\n{\n    Synth carrier;\n    Synth modulator;\n    float frequencyRatio;\n\n    /**\n     * Constructor.\n     * \n     * Params:\n     *   carrier = carrier waveform\n     *   modulator = modulator waveform\n     *   frequencyRatio = frequency multiplier for modulator\n     */\n    this(Synth carrier, Synth modulator, float frequencyRatio)\n    {\n        this.carrier = carrier;\n        this.modulator = modulator;\n        this.frequencyRatio = frequencyRatio;\n    }\n\n    /**\n     * Evaluate FM sample.\n     * \n     * Params:\n     *   sound = a sound object which parameters are used to discretize a sample\n     *   position = sample index\n     *   frequency = signal frequency in Hz\n     */\n    float eval(Sound sound, ulong position, float frequency)\n    {\n        float m = modulator.eval(sound, position, frequency * frequencyRatio);\n        return carrier.eval(sound, position, frequency + m);\n    }\n}\n\n/**\r\n * Fill a given portion of a sound with a signal from specified synthesizer.\n * Params:\n *   sound = a sound object to write to\n *   channel = channel to fill (beginning from 0)\n *   synth = synthesizer object\n *   freq = synthesizer frequency\n *   startTime = start time of a signal in seconds\n *   duration = duration of a signal in seconds\n *   amplitude = volume coefficient of a signal\r\n */\nvoid fillSynth(Sound sound, uint channel, Synth synth, float freq, double startTime, double duration, float amplitude)\n{\n    ulong startSample = cast(ulong)(startTime * sound.sampleRate);\n    ulong endSample = startSample + cast(ulong)(duration * sound.sampleRate);\n    if (endSample >= sound.size)\n        endSample = sound.size - 1;\n\n    foreach(i; startSample..endSample)\r\n    {\n        sound[channel, i] = synth.eval(sound, i - startSample, freq) * amplitude;\r\n    }\n}\n\n/**\r\n * Additively mix a signal from specified synthesizer to a given portion of a sound.\n *   sound = a sound object to write to\n *   channel = channel to fill (beginning from 0)\n *   synth = synthesizer object\n *   freq = synthesizer frequency\n *   startTime = start time of a signal in seconds\n *   duration = duration of a signal in seconds\n *   amplitude = volume coefficient of a signal\r\n */\nvoid mixSynth(Sound sound, uint channel, Synth synth, float freq, double startTime, double duration, float amplitude)\n{\n    ulong startSample = cast(ulong)(startTime * sound.sampleRate);\n    ulong endSample = startSample + cast(ulong)(duration * sound.sampleRate);\n    if (endSample >= sound.size)\n        endSample = sound.size - 1;\n\n    foreach(i; startSample..endSample)\r\n    {\n        float src = sound[channel, i];\n        sound[channel, i] = src + synth.eval(sound, i - startSample, freq) * amplitude;\r\n    }\n}\r\n\r\n/**\r\n * Generate random audio signal.\r\n *   snd = sound\r\n *   ch = channel to fill (beginning from 0)\r\n */\r\nvoid whiteNoise(Sound snd, uint ch)\r\n{\r\n    foreach(i; 0..snd.size)\r\n    {\r\n        snd[ch, i] = uniform(-1.0f, 1.0f);\r\n    }\r\n}\r\n\r\n/**\r\n * Fill the sound with simple sine wave tone.\r\n *   snd = sound\r\n *   ch = channel to fill (beginning from 0)\r\n *   freq = frequency in Hz. For example, a dial tone in Europe is usually 425 Hz\r\n */\r\nvoid sineWave(Sound snd, uint ch, float freq)\r\n{\r\n    float samplePeriod = 1.0f / cast(float)snd.sampleRate;\r\n    foreach(i; 0..snd.size)\r\n    {\r\n        snd[ch, i] = sin(samplePeriod * i * freq * 2.0f * PI);\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/audio/unmanaged.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * GC-free sound class\r\n *\r\n * Copyright: Timur Gafarov 2016-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.audio.unmanaged;\r\n\r\nimport dlib.core.memory;\r\nimport dlib.audio.sample;\r\nimport dlib.audio.sound;\r\n\r\n/**\r\n * An implementation of GenericSound that doesn't use garbage collector\r\n */\r\nclass UnmanagedGenericSound: GenericSound\r\n{\r\n    this(Sound ras)\r\n    {\r\n        super(ras);\r\n    }\r\n\r\n    this(double dur,\r\n         uint freq,\r\n         uint numChannels,\r\n         SampleFormat f)\r\n    {\r\n        super(dur, freq, numChannels, f);\r\n    }\r\n\r\n    this(size_t dataSize,\r\n         ulong numSamples,\r\n         double dur,\r\n         uint numChannels,\r\n         uint freq,\r\n         uint bitdepth,\r\n         SampleFormat f)\r\n    {\r\n        super(dataSize, numSamples, dur, numChannels, freq, bitdepth, f);\r\n    }\r\n\r\n    protected override void allocateData(size_t size)\r\n    {\r\n        _data = New!(ubyte[])(size);\r\n    }\r\n\r\n    override @property Sound dup()\r\n    {\r\n        return New!UnmanagedGenericSound(this);\r\n    }\r\n\r\n    override Sound createSameFormat(uint ch, double dur)\r\n    {\r\n        return New!UnmanagedGenericSound(dur, _sampleRate, ch, _format);\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        if (_data)\r\n            Delete(_data);\r\n    }\r\n}\r\n\r\n/**\r\n * GenericSoundFactory implementation for UnmanagedGenericSound class\r\n */\r\nclass UnmanagedGenericSoundFactory: GenericSoundFactory\r\n{\r\n    UnmanagedGenericSound createSound(\r\n        size_t dataSize,\r\n        ulong numSamples,\r\n        double dur,\r\n        uint numChannels,\r\n        uint freq,\r\n        uint bitdepth,\r\n        SampleFormat f)\r\n    {\r\n        return New!UnmanagedGenericSound(\r\n            dataSize,\r\n            numSamples,\r\n            dur,\r\n            numChannels,\r\n            freq,\r\n            bitdepth,\r\n            f\r\n        );\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/coding/package.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Data coding and compression algorithms\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.coding;\r\n\r\npublic\r\n{\r\n    import dlib.coding.zlib;\r\n    import dlib.coding.varint;\r\n}\r\n"
  },
  {
    "path": "dlib/coding/varint.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Variable-sized integer type\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.coding.varint;\r\n\r\n/**\r\n * Protobuf-style variable-sized integer\r\n */\r\nstruct Varint\r\n{\r\n    ubyte[8] buffer;\r\n    size_t size;\r\n\r\n    /// Returns bytes of an integer\r\n    ubyte[] bytes() return\r\n    {\r\n        return buffer[0..size];\r\n    }\r\n}\r\n\r\n/**\r\n * Returns size in bytes necessary to store an integer\r\n */\r\nsize_t getVarintSize(ulong n)\r\n{\r\n    enum ulong N1 = 128;\r\n    enum ulong N2 = 16384;\r\n    enum ulong N3 = 2097152;\r\n    enum ulong N4 = 268435456;\r\n    enum ulong N5 = 34359738368;\r\n    enum ulong N6 = 4398046511104;\r\n    enum ulong N7 = 562949953421312;\r\n    enum ulong N8 = 72057594037927936;\r\n\r\n    return (\r\n        n < N1 ? 1\r\n      : n < N2 ? 2\r\n      : n < N3 ? 3\r\n      : n < N4 ? 4\r\n      : n < N5 ? 5\r\n      : n < N6 ? 6\r\n      : n < N7 ? 7\r\n      :          8\r\n    );\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(getVarintSize(4637734) == 4);\r\n}\r\n\r\n/**\r\n * Encode a fixed-size integer to Varint\r\n */\r\nVarint encodeVarint(ulong n)\r\n{\r\n    Varint res;\r\n    res.size = getVarintSize(n);\r\n    ubyte* ptr = res.buffer.ptr;\r\n\r\n    for (uint i = 0; i < res.size; i++)\r\n    {\r\n        if (i == res.size - 1)\r\n            *(ptr++) = n & 0xFF;\r\n        else\r\n            *(ptr++) = cast(ubyte)(n | 0x80);\r\n        n >>= 7;\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Varint v = encodeVarint(4783);\r\n    assert(v.size == 2);\r\n    assert(v.bytes == [175, 37]);\r\n}\r\n\r\n/**\r\n * Decode Varing to fixed-size integer\r\n */\r\nulong decodeVarint(Varint vint)\r\n{\r\n    ulong result = 0;\r\n    int bits = 0;\r\n    ubyte* ptr = vint.buffer.ptr;\r\n    ulong ll;\r\n\r\n    while (*ptr & 0x80)\r\n    {\r\n        ll = *ptr;\r\n        result += ((ll & 0x7F) << bits);\r\n        ptr++;\r\n        bits += 7;\r\n    }\r\n\r\n    ll = *ptr;\r\n    result += ((ll & 0x7F) << bits);\r\n\r\n    return result;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Varint v = encodeVarint(2345);\r\n    assert(decodeVarint(v) == 2345);\r\n}\r\n"
  },
  {
    "path": "dlib/coding/zlib.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Zlib compression and decompression\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.coding.zlib;\r\n\r\nimport etc.c.zlib;\r\nimport dlib.core.memory;\r\n\r\n/**\r\n * Zlib encoder that uses provided fixed-length buffer to store results.\r\n * Doesn't use garbage collector\r\n */\r\nstruct ZlibBufferedEncoder\r\n{\r\n    z_stream zlibStream;\r\n    ubyte[] buffer;\r\n    ubyte[] input;\r\n    bool ended = true;\r\n\r\n    /// Constructor\r\n    this(ubyte[] buf, ubyte[] inp)\r\n    {\r\n        buffer = buf;\r\n        input = inp;\r\n        zlibStream.next_out = buffer.ptr;\r\n        zlibStream.avail_out = cast(uint)buffer.length;\r\n        zlibStream.data_type = Z_BINARY;\r\n        zlibStream.zalloc = null;\r\n        zlibStream.zfree = null;\r\n        zlibStream.opaque = null;\r\n\r\n        zlibStream.next_in = inp.ptr;\r\n        zlibStream.avail_in = cast(uint)inp.length;\r\n\r\n        deflateInit(&zlibStream, Z_BEST_COMPRESSION);\r\n        ended = false;\r\n    }\r\n\r\n    /// Encodes the next portion of data and returns a number of bytes written\r\n    size_t encode()\r\n    {\r\n        zlibStream.next_out = buffer.ptr;\r\n        zlibStream.avail_out = cast(uint)buffer.length;\r\n        zlibStream.total_out = 0;\r\n\r\n        while (zlibStream.avail_out > 0)\r\n        {\r\n            int msg = deflate(&zlibStream, Z_FINISH);\r\n\r\n            if (msg == Z_STREAM_END)\r\n            {\r\n                deflateEnd(&zlibStream);\r\n                ended = true;\r\n                return zlibStream.total_out;\r\n            }\r\n            else if (msg != Z_OK)\r\n            {\r\n                deflateEnd(&zlibStream);\r\n                return 0;\r\n            }\r\n        }\r\n\r\n        return zlibStream.total_out;\r\n    }\r\n}\r\n\r\n/**\r\n * Zlib decoder that uses provided buffer to store results\r\n * Doesn't use garbage collector\r\n */\r\nstruct ZlibDecoder\r\n{\r\n    z_stream zlibStream;\r\n    ubyte[] buffer;\r\n    int msg = 0;\r\n\r\n    bool isInitialized = false;\r\n    bool hasEnded = false;\r\n\r\n    /// Constructor\r\n    this(ubyte[] buf)\r\n    {\r\n        buffer = buf;\r\n        zlibStream.next_out = buffer.ptr;\r\n        zlibStream.avail_out = cast(uint)buffer.length;\r\n        zlibStream.data_type = Z_BINARY;\r\n    }\r\n\r\n    /**\r\n     * Decodes a stream. Returns true on success, false on error. \r\n     * Decoded data is stored in buffer field, the buffer is resized if necessary\r\n     */\r\n    bool decode(ubyte[] input)\r\n    {\r\n        zlibStream.next_in = input.ptr;\r\n        zlibStream.avail_in = cast(uint)input.length;\r\n\r\n        if (!isInitialized)\r\n        {\r\n            isInitialized = true;\r\n            msg = inflateInit(&zlibStream);\r\n            if (msg)\r\n            {\r\n                inflateEnd(&zlibStream);\r\n                return false;\r\n            }\r\n        }\r\n\r\n        while (zlibStream.avail_in)\r\n        {\r\n            msg = inflate(&zlibStream, Z_NO_FLUSH);\r\n            if (msg == Z_STREAM_END)\r\n            {\r\n                inflateEnd(&zlibStream);\r\n                hasEnded = true;\r\n                reallocateBuffer(zlibStream.total_out);\r\n                return true;\r\n            }\r\n            else if (msg != Z_OK)\r\n            {\r\n                inflateEnd(&zlibStream);\r\n                return false;\r\n            }\r\n            else if (zlibStream.avail_out == 0)\r\n            {\r\n                reallocateBuffer(buffer.length * 2);\r\n                zlibStream.next_out = &buffer[buffer.length / 2];\r\n                zlibStream.avail_out = cast(uint)(buffer.length / 2);\r\n            }\r\n        }\r\n\r\n        return true;\r\n    }\r\n\r\n    void reallocateBuffer(size_t len)\r\n    {\r\n        ubyte[] buffer2 = New!(ubyte[])(len);\r\n        for(uint i = 0; i < buffer2.length; i++)\r\n            if (i < buffer.length)\r\n                buffer2[i] = buffer[i];\r\n        Delete(buffer);\r\n        buffer = buffer2;\r\n    }\r\n\r\n    /// Call this when you don't need decoded buffer anymore\r\n    void free()\r\n    {\r\n        Delete(buffer);\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/concurrency/package.d",
    "content": "/*\nCopyright (c) 2019-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * A thread pool for running tasks in parallel\n *\n * Copyright: Timur Gafarov 2019-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.concurrency;\n\npublic \n{\n    import dlib.concurrency.threadpool;\n    import dlib.concurrency.workerthread;\n    import dlib.concurrency.taskqueue;\n}"
  },
  {
    "path": "dlib/concurrency/taskqueue.d",
    "content": "/*\nCopyright (c) 2019-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Copyright: Timur Gafarov 2019-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.concurrency.taskqueue;\n\nimport dlib.core.mutex;\nimport dlib.container.array;\nimport dlib.concurrency.workerthread;\n\n/**\n * State of a Task\n */\nenum TaskState\n{\n    Valid = 0,\n    Invalid = 1\n}\n\n/**\n * Task object that encapsulates a delegate\n */\nstruct Task\n{\n    TaskState state = TaskState.Invalid;\n    void delegate() func;\n\n    void run()\n    {\n        if (func !is null)\n        {\n            func();\n        }\n    }\n}\n\n/**\n * Asynchronous task queue\n */\nclass TaskQueue\n{\n    protected:\n    enum size_t MaxTasks = 64;\n    Array!(Task, MaxTasks) tasks;\n    Mutex mutex;\n\n    public:\n    \n    /// Constructor\n    this()\n    {\n        mutex.init();\n    }\n\n    ~this()\n    {\n        tasks.free();\n        mutex.destroy();\n    }\n\n    /// Number of queued tasks\n    size_t count()\n    {\n        return tasks.length;\n    }\n\n    /// Add a task to queue\n    bool enqueue(Task task)\n    {\n        if (tasks.length < MaxTasks)\n        {\n            mutex.lock();\n            tasks.insertFront(task);\n            mutex.unlock();\n            return true;\n        }\n        else\n            return false;\n    }\n\n    /// Remove a task from queue\n    Task dequeue()\n    {\n        if (tasks.length)\n        {\n            mutex.lock();\n            Task t = tasks[tasks.length-1];\n            tasks.removeBack(1);\n            mutex.unlock();\n            return t;\n        }\n        else\n            return Task(TaskState.Invalid, null);\n    }\n}\n"
  },
  {
    "path": "dlib/concurrency/threadpool.d",
    "content": "/*\nCopyright (c) 2019-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Copyright: Timur Gafarov 2019-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.concurrency.threadpool;\n\nimport std.functional;\nimport dlib.core.memory;\nimport dlib.core.mutex;\nimport dlib.concurrency.workerthread;\nimport dlib.concurrency.taskqueue;\n\n/**\n * An object that manages worker threads and runs tasks on them\n */\nclass ThreadPool\n{\n    protected:\n    uint maxThreads;\n    WorkerThread[] workerThreads;\n    TaskQueue taskQueue;\n    bool running = true;\n    Mutex mutex;\n\n    public:\n    \n    /// Constructor\n    this(uint maxThreads)\n    {\n        this.maxThreads = maxThreads;\n        workerThreads = New!(WorkerThread[])(maxThreads);\n        taskQueue = New!TaskQueue();\n\n        mutex.init();\n\n        foreach(i, ref t; workerThreads)\n        {\n            t = New!WorkerThread(i, this);\n            t.start();\n        }\n    }\n\n    ~this()\n    {\n        mutex.lock();\n        running = false;\n        mutex.unlock();\n\n        foreach(i, ref t; workerThreads)\n        {\n            t.join();\n            Delete(t);\n        }\n\n        Delete(taskQueue);\n        Delete(workerThreads);\n\n        mutex.destroy();\n    }\n\n    /// Create a task from delegate\n    Task submit(void delegate() taskDele)\n    {\n        Task task = Task(TaskState.Valid, taskDele);\n        if (!taskQueue.enqueue(task))\n        {\n            task.run();\n        }\n        return task;\n    }\n\n    /// Create a task from function pointer\n    Task submit(void function() taskFunc)\n    {\n        return submit(toDelegate(taskFunc));\n    }\n\n    Task request()\n    {\n        return taskQueue.dequeue();\n    }\n\n    bool isRunning()\n    {\n        return running;\n    }\n\n    /// Returns true if all tasks are finished\n    bool tasksDone()\n    {\n        if (taskQueue.count == 0)\n        {\n            foreach(i, t; workerThreads)\n            {\n                if (t.busy)\n                    return false;\n            }\n\n            return true;\n        }\n        else\n            return false;\n    }\n}\n\n/*\n///\nunittest\n{\n    import std.stdio;\n\n    int x = 0;\n    int y = 0;\n\n    void task1()\n    {\n        while(x < 100)\n            x += 1;\n    }\n\n    void task2()\n    {\n        while(y < 100)\n            y += 1;\n    }\n\n    ThreadPool threadPool = New!ThreadPool(2);\n\n    threadPool.submit(&task1);\n    threadPool.submit(&task2);\n\n    while(!threadPool.tasksDone) {}\n\n    if (x != 100) writeln(x);\n    if (y != 100) writeln(y);\n\n    assert(x == 100);\n    assert(y == 100);\n\n    Delete(threadPool);\n}\n*/\n"
  },
  {
    "path": "dlib/concurrency/workerthread.d",
    "content": "/*\nCopyright (c) 2019-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Copyright: Timur Gafarov 2019-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.concurrency.workerthread;\n\nimport dlib.core.thread;\nimport dlib.concurrency.threadpool;\nimport dlib.concurrency.taskqueue;\n\n/**\n * A thread that is created by ThreadPool\n */\nclass WorkerThread: Thread\n{\n    size_t id;\n    ThreadPool pool;\n    protected bool _busy = false;\n\n    /// Constructor\n    this(size_t id, ThreadPool pool)\n    {\n        super(&threadFunc);\n        this.id = id;\n        this.pool = pool;\n    }\n\n    bool busy()\n    {\n        return _busy;\n    }\n\n    protected void threadFunc()\n    {\n        while(pool.isRunning)\n        {\n            Task task = pool.request();\n            if (task.state != TaskState.Invalid)\n            {\n                _busy = true;\n                task.run();\n                _busy = false;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dlib/container/array.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Dynamic (expandable) array with random access\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov, Roman Vlasov, Andrey Penechko, Eugene Wissner, Roman Chistokhodov, aferust, ijet\r\n */\r\nmodule dlib.container.array;\r\n\r\nimport std.traits;\r\nimport dlib.core.memory;\r\n\r\n/**\r\n * GC-free dynamic array implementation.\r\n * Very efficient for small-sized arrays.\r\n */\r\nstruct Array(T, size_t chunkSize = 32)\r\n{\r\n  private:\r\n    T[chunkSize] staticStorage;\r\n    T[] dynamicStorage;\r\n    uint numChunks = 0;\r\n    uint pos = 0;\r\n\r\n    /**\r\n     * Get pointer to stored data\r\n     */\r\n    private T* storage() nothrow\r\n    {\r\n        if (numChunks == 0)\r\n            return staticStorage.ptr;\r\n        else\r\n            return dynamicStorage.ptr;\r\n    }\r\n\r\n    private const(T)* readOnlyStorage() const nothrow\r\n    {\r\n        if (numChunks == 0)\r\n            return staticStorage.ptr;\r\n        else\r\n            return dynamicStorage.ptr;\r\n    }\r\n\r\n    private void addChunk()\r\n    {\r\n        if (numChunks == 0)\r\n        {\r\n            dynamicStorage = New!(T[])(chunkSize);\r\n        }\r\n        else\r\n        {\r\n            reallocateArray(\r\n                dynamicStorage,\r\n                dynamicStorage.length + chunkSize);\r\n        }\r\n        numChunks++;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!int arr;\r\n        scope(exit) arr.free();\r\n\r\n        assert(arr.length == 0);\r\n        arr.addChunk();\r\n        assert(arr.length == 0);\r\n    }\r\n\r\n  public:\r\n    /**\r\n     * Returns true if the array uses dynamic memory.\r\n     */\r\n    @property bool isDynamic() const\r\n    {\r\n        return dynamicStorage.length > 0;\r\n    }\r\n\r\n    /**\r\n     * Preallocate memory without resizing.\r\n     */\r\n    void reserve(const(size_t) amount)\r\n    {\r\n        if (amount > pos && amount > staticStorage.length)\r\n        {\r\n            if (numChunks == 0)\r\n            {\r\n                dynamicStorage = New!(T[])(amount);\r\n                foreach(i, v; staticStorage)\r\n                    dynamicStorage[i] = v;\r\n            }\r\n            else\r\n            {\r\n                reallocateArray(dynamicStorage, amount);\r\n            }\r\n\r\n            numChunks = cast(uint)(amount / 32 + amount % 32);\r\n        }\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!int arr;\r\n        arr.reserve(100);\r\n        assert(arr.length == 0);\r\n        assert(arr.dynamicStorage.length == 100);\r\n    }\r\n\r\n    /**\r\n     * Resize array and initialize newly added elements with initValue.\r\n     */\r\n    void resize(const(size_t) newLen, T initValue)\r\n    {\r\n        if (newLen > pos)\r\n        {\r\n            reserve(newLen);\r\n            for(size_t i = pos; i < newLen; i++)\r\n            {\r\n                storage[i] = initValue;\r\n            }\r\n        }\r\n\r\n        pos = cast(uint)newLen;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!int arr;\r\n        arr.resize(100, 1);\r\n        assert(arr.length == 100);\r\n        assert(arr[50] == 1);\r\n    }\r\n\r\n    /**\r\n     * Shift contents of array to the right.\r\n     * It inreases the size of array by 1.\r\n     * The first element becomes default initialized.\r\n     */\r\n    void shiftRight()\r\n    {\r\n        insertBack(T.init);\r\n\r\n        for(uint i = pos-1; i > 0; i--)\r\n        {\r\n            storage[i] = storage[i-1];\r\n        }\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!int arr;\r\n        scope(exit) arr.free();\r\n\r\n        arr.shiftRight();\r\n        assert(arr.length == 1);\r\n        assert(arr[0] == int.init);\r\n\r\n        arr[0] = 1;\r\n        arr.insertBack([2,3]);\r\n\r\n        arr.shiftRight();\r\n        assert(arr.length == 4);\r\n        assert(arr[0] == 1);\r\n        assert(arr[1] == 1);\r\n        assert(arr[2] == 2);\r\n        assert(arr[3] == 3);\r\n    }\r\n\r\n    /**\r\n     * Shift contents of array to the left by n positions.\r\n     * Does not change the size of array.\r\n     * n of last elements becomes default initialized.\r\n     */\r\n    void shiftLeft(const(uint) n)\r\n    {\r\n        for(uint i = 0; i < pos; i++)\r\n        {\r\n            if (n + i < pos)\r\n                storage[i] = storage[n + i];\r\n            else\r\n                storage[i] = T.init;\r\n        }\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!int arr;\r\n        scope(exit) arr.free();\r\n\r\n        arr.shiftLeft(1);\r\n        assert(arr.length == 0);\r\n\r\n        arr.insertBack([1,2,3,4,5]);\r\n\r\n        arr.shiftLeft(2);\r\n        assert(arr.length == 5);\r\n        assert(arr[0] == 3);\r\n        assert(arr[1] == 4);\r\n        assert(arr[2] == 5);\r\n        assert(arr[3] == int.init);\r\n        assert(arr[4] == int.init);\r\n    }\r\n\r\n    /**\r\n     * Append single element c to the end.\r\n     */\r\n    void insertBack(T c)\r\n    {\r\n        if (numChunks == 0)\r\n        {\r\n            staticStorage[pos] = c;\r\n            pos++;\r\n            if (pos == chunkSize)\r\n            {\r\n                addChunk();\r\n                foreach(i, ref v; dynamicStorage)\r\n                    v = staticStorage[i];\r\n            }\r\n        }\r\n        else\r\n        {\r\n            if (pos == dynamicStorage.length)\r\n                addChunk();\r\n\r\n            dynamicStorage[pos] = c;\r\n            pos++;\r\n        }\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!int arr;\r\n        scope(exit) arr.free();\r\n\r\n        foreach(i; 0..16) {\r\n            arr.insertBack(i);\r\n        }\r\n        assert(arr.length == 16);\r\n        arr.insertBack(16);\r\n        assert(arr.length == 17);\r\n        assert(arr[16] == 16);\r\n    }\r\n\r\n    /**\r\n     * Append element to the start.\r\n     */\r\n    void insertFront(T c)\r\n    {\r\n        shiftRight();\r\n        storage[0] = c;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!int arr;\r\n        scope(exit) arr.free();\r\n\r\n        arr.insertFront(1);\r\n        arr.insertBack(2);\r\n        arr.insertFront(0);\r\n        assert(arr.data == [0,1,2]);\r\n    }\r\n\r\n    /**\r\n     * Append all elements of slice s to the end.\r\n     */\r\n    void insertBack(const(T)[] s)\r\n    {\r\n        foreach(c; s)\r\n            insertBack(cast(T)c);\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!int arr;\r\n        scope(exit) arr.free();\r\n\r\n        arr.insertBack([1,2,3,4]);\r\n        assert(arr.data == [1,2,3,4]);\r\n        arr.insertBack([5,6,7,8]);\r\n        assert(arr.data == [1,2,3,4,5,6,7,8]);\r\n    }\r\n\r\n    /**\r\n     * Append all elements of slice s to the start.\r\n     */\r\n    void insertFront(const(T)[] s)\r\n    {\r\n        foreach_reverse(c; s)\r\n            insertFront(cast(T)c);\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!int arr;\r\n        scope(exit) arr.free();\r\n\r\n        arr.insertFront([5,6,7,8]);\r\n        assert(arr.data == [5,6,7,8]);\r\n        arr.insertFront([1,2,3,4]);\r\n        assert(arr.data == [1,2,3,4,5,6,7,8]);\r\n    }\r\n\r\n    /**\r\n     * Same as insertBack, but in operator form.\r\n     */\r\n    auto opOpAssign(string op)(T c) if (op == \"~\")\r\n    {\r\n        insertBack(c);\r\n        return this;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!int arr;\r\n        scope(exit) arr.free();\r\n\r\n        arr ~= 1;\r\n        arr ~= 2;\r\n        assert(arr.data == [1,2]);\r\n    }\r\n\r\n    /**\r\n     * Same as insertBack, but in operator form.\r\n     */\r\n    auto opOpAssign(string op)(const(T)[] s) if (op == \"~\")\r\n    {\r\n        insertBack(s);\r\n        return this;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!int arr;\r\n        scope(exit) arr.free();\r\n\r\n        arr ~= [1,2,3];\r\n        assert(arr.data == [1,2,3]);\r\n    }\r\n\r\n    /**\r\n     * Remove n of elements from the end.\r\n     * Returns: number of removed elements.\r\n     */\r\n    uint removeBack(const(uint) n)\r\n    {\r\n        if (pos == n)\r\n        {\r\n            pos = 0;\r\n            return n;\r\n        }\r\n        else if (pos >= n)\r\n        {\r\n            pos -= n;\r\n            return n;\r\n        }\r\n        else\r\n        {\r\n            uint res = pos;\r\n            pos = 0;\r\n            return res;\r\n        }\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!int arr;\r\n        scope(exit) arr.free();\r\n\r\n        arr.insertBack([1,2,3]);\r\n        assert(arr.removeBack(3) == 3);\r\n        assert(arr.length == 0);\r\n\r\n        arr.insertBack([1,2,3,4]);\r\n        assert(arr.removeBack(2) == 2);\r\n        assert(arr.data == [1,2]);\r\n\r\n        assert(arr.removeBack(3) == 2);\r\n        assert(arr.length == 0);\r\n    }\r\n\r\n    /**\r\n     * Remove n of elements from the start.\r\n     * Returns: number of removed elements.\r\n     */\r\n    uint removeFront(const(uint) n)\r\n    {\r\n        if (pos == n)\r\n        {\r\n            pos = 0;\r\n            return n;\r\n        }\r\n        else if (pos > n)\r\n        {\r\n            shiftLeft(n);\r\n            pos -= n;\r\n            return n;\r\n        }\r\n        else\r\n        {\r\n            uint res = pos;\r\n            pos = 0;\r\n            return res;\r\n        }\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!int arr;\r\n        scope(exit) arr.free();\r\n\r\n        arr.insertBack([1,2,3]);\r\n        assert(arr.removeFront(3) == 3);\r\n\r\n        arr.insertBack([1,2,3,4]);\r\n        assert(arr.removeFront(2) == 2);\r\n        assert(arr.data == [3,4]);\r\n\r\n        assert(arr.removeFront(3) == 2);\r\n        assert(arr.length == 0);\r\n    }\r\n\r\n    /**\r\n     * Inserts an element by a given index\r\n     * (resizing an array and shifting elements).\r\n     */\r\n    void insertKey(const(size_t) i, T v)\r\n    {\r\n        if (i < pos)\r\n        {\r\n            T* s = storage();\r\n\r\n            insertBack(T.init);\r\n\r\n            for (size_t p = pos-1; p > i; p--)\r\n            {\r\n                s[p] = s[p-1];\r\n            }\r\n\r\n            s[i] = v;\r\n        }\r\n    }\r\n\r\n    unittest\r\n    {\r\n        Array!int arr;\r\n        scope(exit) arr.free();\r\n\r\n        arr.insertBack([1, 4, 5]);\r\n\r\n        arr.insertKey(1, 7);\r\n        assert(arr.length == 4);\r\n        assert(arr.data == [1, 7, 4, 5]);\r\n    }\r\n\r\n    /**\r\n     * Removes an element by a given index.\r\n     */\r\n    void removeKey(const(size_t) i)\r\n    {\r\n        if (i < pos)\r\n        {\r\n            T* s = storage();\r\n            for (size_t p = i+1; p <= pos; p++)\r\n            {\r\n                s[p-1] = s[p];\r\n            }\r\n\r\n            pos--;\r\n        }\r\n    }\r\n\r\n    unittest\r\n    {\r\n        Array!int arr;\r\n        scope(exit) arr.free();\r\n\r\n        arr.insertBack([1, 4, 5]);\r\n\r\n        arr.removeKey(1);\r\n        assert(arr.length == 2);\r\n        assert(arr.data == [1, 5]);\r\n    }\r\n\r\n    alias insertAt = insertKey;\r\n    alias removeAt = removeKey;\r\n\r\n    /**\r\n     * If obj is in array, remove its first occurence and return true.\r\n     * Otherwise do nothing and return false.\r\n     */\r\n    bool removeFirst(T obj)\r\n    {\r\n        size_t index;\r\n        bool found = false;\r\n\r\n        for (size_t i = 0; i < data.length; i++)\r\n        {\r\n            T o = data[i];\r\n\r\n            static if (isArray!T)\r\n            {\r\n                if (o[] == obj[])\r\n                {\r\n                    index = i;\r\n                    found = true;\r\n                    break;\r\n                }\r\n            }\r\n            else\r\n            {\r\n                if (o is obj)\r\n                {\r\n                    index = i;\r\n                    found = true;\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n\r\n        if (found)\r\n        {\r\n            removeKey(index);\r\n            return true;\r\n        }\r\n\r\n        return false;\r\n    }\r\n\r\n    // For backward compatibility\r\n    alias append = insertBack;\r\n    alias appendLeft = insertFront;\r\n    alias remove = removeBack;\r\n    alias removeLeft = removeFront;\r\n\r\n    /**\r\n     * Get number of elements in array.\r\n     */\r\n    size_t length() const nothrow\r\n    {\r\n        return pos;\r\n    }\r\n\r\n    alias opDollar = length;\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!int arr;\r\n        scope(exit) arr.free();\r\n\r\n        arr.insertBack([1,2,3]);\r\n        assert(arr.length == 3);\r\n    }\r\n\r\n    /**\r\n     * Get slice of data\r\n     */\r\n    T[] data() nothrow\r\n    {\r\n        return storage[0..pos];\r\n    }\r\n\r\n    const(T)[] readOnlyData() const nothrow\r\n    {\r\n        return readOnlyStorage[0..pos];\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!(int,4) arr;\r\n        scope(exit) arr.free();\r\n\r\n        foreach(i; 0..6) {\r\n            arr.insertBack(i);\r\n        }\r\n\r\n        assert(arr.data == [0,1,2,3,4,5]);\r\n    }\r\n\r\n    /**\r\n     * Access element by index.\r\n     */\r\n    T opIndex(const(size_t) index)\r\n    {\r\n        return data[index];\r\n    }\r\n\r\n    /**\r\n     * Access by slice.\r\n     */\r\n    T[] opSlice(const(size_t) start, const(size_t) end)\r\n    {\r\n        return data[start..end];\r\n    }\r\n\r\n    /**\r\n     * Set element t for index.\r\n     */\r\n    T opIndexAssign(T t, const(size_t) index)\r\n    {\r\n        data[index] = t;\r\n        return t;\r\n    }\r\n\r\n    /**\r\n     * Iterating over array via foreach.\r\n     */\r\n    int opApply(scope int delegate(size_t i, ref T) dg)\r\n    {\r\n        int result = 0;\r\n\r\n        foreach(i, ref v; data)\r\n        {\r\n            result = dg(i, v);\r\n            if (result)\r\n                break;\r\n        }\r\n\r\n        return result;\r\n    }\r\n\r\n    /**\r\n     * Iterating over array via foreach_reverse.\r\n     */\r\n    int opApplyReverse(scope int delegate(size_t i, ref T) dg)\r\n    {\r\n        int result = 0;\r\n\r\n        for(size_t i =  length; i-- > 0; )\r\n        {\r\n            result = dg(i, data[i]);\r\n            if (result)\r\n                break;\r\n        }\r\n\r\n        return result;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!(int,4) arr;\r\n        scope(exit) arr.free();\r\n\r\n        int[4] values;\r\n        arr.insertBack([1,2,3,4]);\r\n        foreach(i, ref val; arr) {\r\n            values[i] = val;\r\n            if(values[i] == 4) {\r\n                break;\r\n            }\r\n        }\r\n        assert(values[] == arr.data);\r\n    }\r\n\r\n    /**\r\n     * Iterating over array via foreach.\r\n     */\r\n    int opApply(scope int delegate(ref T) dg)\r\n    {\r\n        int result = 0;\r\n\r\n        foreach(i, ref v; data)\r\n        {\r\n            result = dg(v);\r\n            if (result)\r\n                break;\r\n        }\r\n\r\n        return 0;\r\n    }\r\n\r\n     /**\r\n     * Iterating over array via foreach_reverse.\r\n     */\r\n    int opApplyReverse(scope int delegate(ref T) dg)\r\n    {\r\n        int result = 0;\r\n\r\n        for(size_t i = length; i-- > 0; )\r\n        {\r\n            result = dg(data[i]);\r\n            if (result)\r\n                break;\r\n        }\r\n\r\n        return result;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Array!(int,4) arr;\r\n        scope(exit) arr.free();\r\n\r\n        int[] values;\r\n        arr.insertBack([1,2,3,4]);\r\n        foreach(ref val; arr) {\r\n            values ~= val;\r\n        }\r\n        assert(values[] == arr.data);\r\n    }\r\n\r\n    /**\r\n     * Free dynamically allocated memory used by array.\r\n     */\r\n    void free()\r\n    {\r\n        if (dynamicStorage.length)\r\n            Delete(dynamicStorage);\r\n        numChunks = 0;\r\n        pos = 0;\r\n    }\r\n}\r\n\r\nvoid reallocateArray(T)(ref T[] buffer, const(size_t) len)\r\n{\r\n    T[] buffer2 = New!(T[])(len);\r\n    for(uint i = 0; i < buffer2.length; i++)\r\n        if (i < buffer.length)\r\n            buffer2[i] = buffer[i];\r\n    Delete(buffer);\r\n    buffer = buffer2;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    auto arr = New!(int[])(3);\r\n    arr[0] = 1; arr[1] = 2; arr[2] = 3;\r\n\r\n    reallocateArray(arr, 2);\r\n    assert(arr.length == 2);\r\n    assert(arr[0] == 1);\r\n    assert(arr[1] == 2);\r\n\r\n    reallocateArray(arr, 4);\r\n    assert(arr.length == 4);\r\n    assert(arr[0] == 1);\r\n    assert(arr[1] == 2);\r\n}\r\n"
  },
  {
    "path": "dlib/container/bst.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Binary search tree\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov, Andrey Penechko\r\n */\r\nmodule dlib.container.bst;\r\n\r\nimport dlib.core.memory;\r\n\r\n/**\r\n * GC-free binary search tree implementation.\r\n */\r\nclass BST(T)\r\n{\r\n    bool root;\r\n    BST left = null;\r\n    BST right = null;\r\n    int key = 0;\r\n\r\n    T value;\r\n\r\n    this()\r\n    {\r\n        root = true;\r\n    }\r\n\r\n    this(int k, T v)\r\n    {\r\n        key = k;\r\n        value = v;\r\n        root = false;\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        clear();\r\n    }\r\n\r\n    void insert(int k, T v)\r\n    {\r\n        if (k < key)\r\n        {\r\n            if (left is null) left = allocate!(BST)(k, v);\r\n            else left.insert(k, v);\r\n        }\r\n        else if (k > key)\r\n        {\r\n            if (right is null) right = allocate!(BST)(k, v);\r\n            else right.insert(k, v);\r\n        }\r\n        else value = v;\r\n    }\r\n\r\n    BST find(int k)\r\n    {\r\n        if (k < key)\r\n        {\r\n            if (left !is null) return left.find(k);\r\n            else return null;\r\n        }\r\n        else if (k > key)\r\n        {\r\n            if (right !is null) return right.find(k);\r\n            else return null;\r\n        }\r\n        else return this;\r\n    }\r\n\r\n    protected BST findLeftMost()\r\n    {\r\n        if (left is null) return this;\r\n        else return left.findLeftMost();\r\n    }\r\n\r\n    void remove(int k, BST par = null)\r\n    {\r\n        if (k < key)\r\n        {\r\n            if (left !is null) left.remove(k, this);\r\n            else return;\r\n        }\r\n        else if (k > key)\r\n        {\r\n            if (right !is null) right.remove(k, this);\r\n            else return;\r\n        }\r\n        else\r\n        {\r\n            if (left !is null && right !is null)\r\n            {\r\n                auto m = right.findLeftMost();\r\n                key = m.key;\r\n                value = m.value;\r\n                right.remove(key, this);\r\n            }\r\n            else if (this == par.left)\r\n            {\r\n                par.left = (left !is null)? left : right;\r\n            }\r\n            else if (this == par.right)\r\n            {\r\n                par.right = (left !is null)? left : right;\r\n            }\r\n        }\r\n    }\r\n\r\n    void traverse(void function(int, T) func)\r\n    {\r\n        if (left !is null)\r\n            left.traverse(func);\r\n        if (!root)\r\n            func(key, value);\r\n        if (right !is null)\r\n            right.traverse(func);\r\n    }\r\n\r\n    int opApply(scope int delegate(int, ref T) dg)\r\n    {\r\n        int result = 0;\r\n\r\n        if (left !is null)\r\n        {\r\n            result = left.opApply(dg);\r\n            if (result)\r\n                return result;\r\n        }\r\n\r\n        if (!root)\r\n            dg(key, value);\r\n\r\n        if (right !is null)\r\n        {\r\n            result = right.opApply(dg);\r\n            if (result)\r\n                return result;\r\n        }\r\n\r\n        return result;\r\n    }\r\n\r\n    void clear()\r\n    {\r\n        if (left !is null)\r\n        {\r\n            left.clear();\r\n            deallocate(left);\r\n            left = null;\r\n        }\r\n        if (right !is null)\r\n        {\r\n            right.clear();\r\n            deallocate(right);\r\n            right = null;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/container/buffer.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Eugene Wissner\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Copyright: Eugene Wissner 2016-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Eugene Wissner\r\n */\r\nmodule dlib.container.buffer;\r\n\r\nimport dlib.memory;\r\n\r\nversion (unittest)\r\n{\r\n    private int fillBuffer(ubyte[] buffer,\r\n                           in size_t size,\r\n                           int start = 0,\r\n                           int end = 10) @nogc pure nothrow\r\n    in\r\n    {\r\n        assert(start < end);\r\n    }\r\n    do\r\n    {\r\n        auto numberRead = end - start;\r\n        for (ubyte i; i < numberRead; ++i)\r\n        {\r\n            buffer[i] = cast(ubyte) (start + i);\r\n        }\r\n        return numberRead;\r\n    }\r\n}\r\n\r\n/**\r\n * Interface for implemeting input/output buffers.\r\n */\r\ninterface Buffer\r\n{\r\n    /**\r\n     * Returns: The size of the internal buffer.\r\n     */\r\n    @property size_t capacity() const @nogc @safe pure nothrow;\r\n\r\n    /**\r\n     * Returns: Data size.\r\n     */\r\n    @property size_t length() const @nogc @safe pure nothrow;\r\n\r\n    /**\r\n     * Returns: Available space.\r\n     */\r\n    @property size_t free() const @nogc @safe pure nothrow;\r\n\r\n    /**\r\n     * Params:\r\n     *     start = Start position.\r\n     *     end   = End position.\r\n     *\r\n     * Returns: Array between $(D_PARAM start) and $(D_PARAM end).\r\n     */\r\n    @property ubyte[] opSlice(size_t start, size_t end)\r\n    in\r\n    {\r\n        assert(start <= end);\r\n        assert(end <= length);\r\n    }\r\n\r\n    /**\r\n    * Returns: Length of available data.\r\n    */\r\n    @property size_t opDollar() const pure nothrow @safe @nogc;\r\n\r\n    /**\r\n     * Returns: Data chunk.\r\n     */\r\n    @property ubyte[] opIndex();\r\n}\r\n\r\n/**\r\n * Self-expanding buffer, that can be used with functions returning the number\r\n * of the read bytes.\r\n *\r\n * This buffer supports asynchronous reading. It means you can pass a new chunk\r\n * to an asynchronous read function during you are working with already\r\n * available data. But only one asynchronous call at a time is supported. Be\r\n * sure to call $(D_PSYMBOL ReadBuffer.clear()) before you append the result\r\n * of the pended asynchronous call.\r\n */\r\nclass ReadBuffer : Buffer\r\n{\r\n    /// Internal buffer.\r\n    protected ubyte[] buffer_;\r\n\r\n    /// Filled buffer length.\r\n    protected size_t length_;\r\n\r\n    /// Start of available data.\r\n    protected size_t start;\r\n\r\n    /// Last position returned with $(D_KEYWORD []).\r\n    protected size_t ring;\r\n\r\n    /// Available space.\r\n    protected immutable size_t minAvailable;\r\n\r\n    /// Size by which the buffer will grow.\r\n    protected immutable size_t blockSize;\r\n\r\n    invariant\r\n    {\r\n        assert(length_ <= buffer_.length);\r\n        assert(blockSize > 0);\r\n        assert(minAvailable > 0);\r\n    }\r\n\r\n    /**\r\n     * Creates a new read buffer.\r\n     *\r\n     * Params:\r\n     *     size         = Initial buffer size and the size by which the buffer\r\n     *                    will grow.\r\n     *     minAvailable = minimal size should be always  available to fill.\r\n     *                    So it will reallocate if $(D_INLINECODE\r\n     *                    $(D_PSYMBOL free) < $(D_PARAM minAvailable)\r\n     *                    ).\r\n     */\r\n    this(size_t size = 8192,\r\n         size_t minAvailable = 1024)\r\n    {\r\n        this.minAvailable = minAvailable;\r\n        this.blockSize = size;\r\n        defaultAllocator.resizeArray!ubyte(buffer_, size);\r\n    }\r\n\r\n    /**\r\n     * Deallocates the internal buffer.\r\n     */\r\n    ~this()\r\n    {\r\n        defaultAllocator.dispose(buffer_);\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        auto b = defaultAllocator.make!ReadBuffer;\r\n        assert(b.capacity == 8192);\r\n        assert(b.length == 0);\r\n\r\n        defaultAllocator.dispose(b);\r\n    }\r\n\r\n    /**\r\n     * Returns: The size of the internal buffer.\r\n     */\r\n    @property size_t capacity() const @nogc @safe pure nothrow\r\n    {\r\n        return buffer_.length;\r\n    }\r\n\r\n    /**\r\n     * Returns: Data size.\r\n     */\r\n    @property size_t length() const @nogc @safe pure nothrow\r\n    {\r\n        return length_ - start;\r\n    }\r\n\r\n    /**\r\n     * Clears the buffer.\r\n     *\r\n     * Returns: $(D_KEYWORD this).\r\n     */\r\n    ReadBuffer clear() pure nothrow @safe @nogc\r\n    {\r\n        start = length_ = ring;\r\n        return this;\r\n    }\r\n\r\n    /**\r\n     * Returns: Available space.\r\n     */\r\n    @property size_t free() const pure nothrow @safe @nogc\r\n    {\r\n        return length > ring ? capacity - length : capacity - ring;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        auto b = defaultAllocator.make!ReadBuffer;\r\n        size_t numberRead;\r\n\r\n        // Fills the buffer with values 0..10\r\n        assert(b.free == b.blockSize);\r\n\r\n        numberRead = fillBuffer(b[], b.free, 0, 10);\r\n        b += numberRead;\r\n        assert(b.free == b.blockSize - numberRead);\r\n        b.clear();\r\n        assert(b.free == b.blockSize);\r\n\r\n        defaultAllocator.dispose(b);\r\n    }\r\n\r\n    /**\r\n     * Appends some data to the buffer.\r\n     *\r\n     * Params:\r\n     *     length = Number of the bytes read.\r\n     *\r\n     * Returns: $(D_KEYWORD this).\r\n     */\r\n    ReadBuffer opOpAssign(string op)(size_t length)\r\n        if (op == \"+\")\r\n    {\r\n        length_ += length;\r\n        ring = start;\r\n        return this;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        auto b = defaultAllocator.make!ReadBuffer;\r\n        size_t numberRead;\r\n        ubyte[] result;\r\n\r\n        // Fills the buffer with values 0..10\r\n        numberRead = fillBuffer(b[], b.free, 0, 10);\r\n        b += numberRead;\r\n\r\n        result = b[0..$];\r\n        assert(result[0] == 0);\r\n        assert(result[1] == 1);\r\n        assert(result[9] == 9);\r\n        b.clear();\r\n\r\n        // It shouldn't overwrite, but append another 5 bytes to the buffer\r\n        numberRead = fillBuffer(b[], b.free, 0, 10);\r\n        b += numberRead;\r\n\r\n        numberRead = fillBuffer(b[], b.free, 20, 25);\r\n        b += numberRead;\r\n\r\n        result = b[0..$];\r\n        assert(result[0] == 0);\r\n        assert(result[1] == 1);\r\n        assert(result[9] == 9);\r\n        assert(result[10] == 20);\r\n        assert(result[14] == 24);\r\n\r\n        defaultAllocator.dispose(b);\r\n    }\r\n\r\n    /**\r\n     * Returns: Length of available data.\r\n     */\r\n    @property size_t opDollar() const pure nothrow @safe @nogc\r\n    {\r\n        return length;\r\n    }\r\n\r\n    /**\r\n     * Params:\r\n     *     start = Start position.\r\n     *     end   = End position.\r\n     *\r\n     * Returns: Array between $(D_PARAM start) and $(D_PARAM end).\r\n     */\r\n    @property ubyte[] opSlice(size_t start, size_t end) pure nothrow @safe @nogc\r\n    {\r\n        return buffer_[this.start + start .. this.start + end];\r\n    }\r\n\r\n    /**\r\n     * Returns a free chunk of the buffer.\r\n     *\r\n     * Add ($(D_KEYWORD +=)) the number of the read bytes after using it.\r\n     *\r\n     * Returns: A free chunk of the buffer.\r\n     */\r\n    ubyte[] opIndex()\r\n    {\r\n        if (start > 0)\r\n        {\r\n            auto ret = buffer_[0..start];\r\n            ring = 0;\r\n            return ret;\r\n        }\r\n        else\r\n        {\r\n            if (capacity - length < minAvailable)\r\n            {\r\n                defaultAllocator.resizeArray!ubyte(buffer_, capacity + blockSize);\r\n            }\r\n            ring = length_;\r\n            return buffer_[length_..$];\r\n        }\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        auto b = defaultAllocator.make!ReadBuffer;\r\n        size_t numberRead;\r\n        ubyte[] result;\r\n\r\n        // Fills the buffer with values 0..10\r\n        numberRead = fillBuffer(b[], b.free, 0, 10);\r\n        b += numberRead;\r\n\r\n        assert(b.length == 10);\r\n        result = b[0..$];\r\n        assert(result[0] == 0);\r\n        assert(result[9] == 9);\r\n        b.clear();\r\n        assert(b.length == 0);\r\n\r\n        defaultAllocator.dispose(b);\r\n    }\r\n}\r\n\r\n/**\r\n * Circular, self-expanding buffer with overflow support. Can be used with\r\n * functions returning returning the number of the transferred bytes.\r\n *\r\n * The buffer is optimized for situations where you read all the data from it\r\n * at once (without writing to it occasionally). It can become ineffective if\r\n * you permanently keep some data in the buffer and alternate writing and\r\n * reading, because it may allocate and move elements.\r\n */\r\nclass WriteBuffer : Buffer\r\n{\r\n    /// Internal buffer.\r\n    protected ubyte[] buffer_;\r\n\r\n    /// Buffer start position.\r\n    protected size_t start;\r\n\r\n    /// Buffer ring area size. After this position begins buffer overflow area.\r\n    protected size_t ring;\r\n\r\n    /// Size by which the buffer will grow.\r\n    protected immutable size_t blockSize;\r\n\r\n    /// The position of the free area in the buffer.\r\n    protected size_t position;\r\n\r\n    invariant\r\n    {\r\n        assert(blockSize > 0);\r\n        // position can refer to an element outside the buffer if the buffer is full.\r\n        assert(position <= buffer_.length);\r\n    }\r\n\r\n    /**\r\n     * Params:\r\n     *  size = Initial buffer size and the size by which the buffer\r\n     *            will grow.\r\n     */\r\n    this(size_t size = 8192)\r\n    {\r\n        blockSize = size;\r\n        ring = size - 1;\r\n        defaultAllocator.resizeArray!ubyte(buffer_, size);\r\n    }\r\n\r\n    /**\r\n     * Deallocates the internal buffer.\r\n     */\r\n    ~this()\r\n    {\r\n        defaultAllocator.dispose(buffer_);\r\n    }\r\n\r\n    /**\r\n     * Returns: The size of the internal buffer.\r\n     */\r\n    @property size_t capacity() const @nogc @safe pure nothrow\r\n    {\r\n        return buffer_.length;\r\n    }\r\n\r\n    /**\r\n     * Note that $(D_PSYMBOL length) doesn't return the real length of the data,\r\n     * but only the array length that will be returned with $(D_PSYMBOL buffer)\r\n     * next time. Be sure to call $(D_PSYMBOL buffer) and set $(D_KEYWORD +=)\r\n     * until $(D_PSYMBOL length) returns 0.\r\n     *\r\n     * Returns: Data size.\r\n     */\r\n    @property size_t length() const @nogc @safe pure nothrow\r\n    {\r\n        if (position > ring || position < start) // Buffer overflowed\r\n        {\r\n            return ring - start + 1;\r\n        }\r\n        else\r\n        {\r\n            return position - start;\r\n        }\r\n    }\r\n\r\n    /**\r\n    * Returns: Length of available data.\r\n    */\r\n    @property size_t opDollar() const pure nothrow @safe @nogc\r\n    {\r\n        return length;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        auto b = defaultAllocator.make!WriteBuffer(4);\r\n        ubyte[3] buf = [48, 23, 255];\r\n\r\n        b ~= buf;\r\n        assert(b.length == 3);\r\n        b += 2;\r\n        assert(b.length == 1);\r\n\r\n        b ~= buf;\r\n        assert(b.length == 2);\r\n        b += 2;\r\n        assert(b.length == 2);\r\n\r\n        b ~= buf;\r\n        assert(b.length == 5);\r\n        b += b.length;\r\n        assert(b.length == 0);\r\n\r\n        defaultAllocator.dispose(b);\r\n    }\r\n\r\n    /**\r\n     * Returns: Available space.\r\n     */\r\n    @property size_t free() const @nogc @safe pure nothrow\r\n    {\r\n        return capacity - length;\r\n    }\r\n\r\n    /**\r\n     * Appends data to the buffer.\r\n     *\r\n     * Params:\r\n     *     buffer = Buffer chunk got with $(D_PSYMBOL buffer).\r\n     */\r\n    WriteBuffer opOpAssign(string op)(ubyte[] buffer)\r\n        if (op == \"~\")\r\n    {\r\n        size_t end, start;\r\n\r\n        if (position >= this.start && position <= ring)\r\n        {\r\n            auto afterRing = ring + 1;\r\n\r\n            end = position + buffer.length;\r\n            if (end > afterRing)\r\n            {\r\n                end = afterRing;\r\n            }\r\n            start = end - position;\r\n            buffer_[position..end] = buffer[0..start];\r\n            if (end == afterRing)\r\n            {\r\n                position = this.start == 0 ? afterRing : 0;\r\n            }\r\n            else\r\n            {\r\n                position = end;\r\n            }\r\n        }\r\n\r\n        // Check if we have some free space at the beginning\r\n        if (start < buffer.length && position < this.start)\r\n        {\r\n            end = position + buffer.length - start;\r\n            if (end > this.start)\r\n            {\r\n                end = this.start;\r\n            }\r\n            auto areaEnd = end - position + start;\r\n            buffer_[position..end] = buffer[start..areaEnd];\r\n            position = end == this.start ? ring + 1 : end - position;\r\n            start = areaEnd;\r\n        }\r\n\r\n        // And if we still haven't found any place, save the rest in the overflow area\r\n        if (start < buffer.length)\r\n        {\r\n            end = position + buffer.length - start;\r\n            if (end > capacity)\r\n            {\r\n                auto newSize = end / blockSize * blockSize + blockSize;\r\n\r\n                defaultAllocator.resizeArray!ubyte(buffer_, newSize);\r\n            }\r\n            buffer_[position..end] = buffer[start..$];\r\n            position = end;\r\n            if (this.start == 0)\r\n            {\r\n                ring = capacity - 1;\r\n            }\r\n        }\r\n\r\n        return this;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        auto b = defaultAllocator.make!WriteBuffer(4);\r\n        ubyte[3] buf = [48, 23, 255];\r\n\r\n        b ~= buf;\r\n        assert(b.capacity == 4);\r\n        assert(b.buffer_[0] == 48 && b.buffer_[1] == 23 && b.buffer_[2] == 255);\r\n\r\n        b += 2;\r\n        b ~= buf;\r\n        assert(b.capacity == 4);\r\n        assert(b.buffer_[0] == 23 && b.buffer_[1] == 255\r\n            && b.buffer_[2] == 255 && b.buffer_[3] == 48);\r\n\r\n        b += 2;\r\n        b ~= buf;\r\n        assert(b.capacity == 8);\r\n        assert(b.buffer_[0] == 23 && b.buffer_[1] == 255\r\n            && b.buffer_[2] == 48 && b.buffer_[3] == 23 && b.buffer_[4] == 255);\r\n\r\n        defaultAllocator.dispose(b);\r\n\r\n        b = make!WriteBuffer(defaultAllocator, 2);\r\n\r\n        b ~= buf;\r\n        assert(b.start == 0);\r\n        assert(b.capacity == 4);\r\n        assert(b.ring == 3);\r\n        assert(b.position == 3);\r\n\r\n        defaultAllocator.dispose(b);\r\n    }\r\n\r\n    /**\r\n     * Sets how many bytes were written. It will shrink the buffer\r\n     * appropriately. Always set this property after calling\r\n     * $(D_PSYMBOL buffer).\r\n     *\r\n     * Params:\r\n     *     length = Length of the written data.\r\n     *\r\n     * Returns: $(D_KEYWORD this).\r\n     */\r\n    @property WriteBuffer opOpAssign(string op)(size_t length) pure nothrow @safe @nogc\r\n        if (op == \"+\")\r\n    in\r\n    {\r\n        assert(length <= this.length);\r\n    }\r\n    do\r\n    {\r\n        auto afterRing = ring + 1;\r\n        auto oldStart = start;\r\n\r\n        if (length <= 0)\r\n        {\r\n            return this;\r\n        }\r\n        else if (position <= afterRing)\r\n        {\r\n            start += length;\r\n            if (start > 0 && position == afterRing)\r\n            {\r\n                position = oldStart;\r\n            }\r\n        }\r\n        else\r\n        {\r\n            auto overflow = position - afterRing;\r\n\r\n            if (overflow > length) {\r\n                buffer_[start.. start + length] = buffer_[afterRing.. afterRing + length];\r\n                buffer_[afterRing.. afterRing + length] = buffer_[afterRing + length ..position];\r\n                position -= length;\r\n            }\r\n            else if (overflow == length)\r\n            {\r\n                buffer_[start.. start + overflow] = buffer_[afterRing..position];\r\n                position -= overflow;\r\n            }\r\n            else\r\n            {\r\n                buffer_[start.. start + overflow] = buffer_[afterRing..position];\r\n                position = overflow;\r\n            }\r\n            start += length;\r\n\r\n            if (start == position)\r\n            {\r\n                if (position != afterRing)\r\n                {\r\n                    position = 0;\r\n                }\r\n                start = 0;\r\n                ring = capacity - 1;\r\n            }\r\n        }\r\n        if (start > ring)\r\n        {\r\n            start = 0;\r\n        }\r\n        return this;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        auto b = defaultAllocator.make!WriteBuffer;\r\n        ubyte[6] buf = [23, 23, 255, 128, 127, 9];\r\n\r\n        b ~= buf;\r\n        assert(b.length == 6);\r\n        b += 2;\r\n        assert(b.length == 4);\r\n        b += 4;\r\n        assert(b.length == 0);\r\n\r\n        defaultAllocator.dispose(b);\r\n    }\r\n\r\n    /**\r\n     * Returns a chunk with data.\r\n     *\r\n     * After calling it, set $(D_KEYWORD +=) to the length could be\r\n     * written.\r\n     *\r\n     * $(D_PSYMBOL buffer) may return only part of the data. You may need\r\n     * to call it (and set $(D_KEYWORD +=) several times until\r\n     * $(D_PSYMBOL length) is 0). If all the data can be written,\r\n     * maximally 3 calls are required.\r\n     *\r\n     * Returns: A chunk of data buffer.\r\n     */\r\n    @property ubyte[] opSlice(size_t start, size_t end) pure nothrow @safe @nogc\r\n    {\r\n        immutable internStart = this.start + start;\r\n\r\n        if (position > ring || position < start) // Buffer overflowed\r\n        {\r\n            return buffer_[this.start.. ring + 1 - length + end];\r\n        }\r\n        else\r\n        {\r\n            return buffer_[this.start.. this.start + end];\r\n        }\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        auto b = defaultAllocator.make!WriteBuffer(6);\r\n        ubyte[6] buf = [23, 23, 255, 128, 127, 9];\r\n\r\n        b ~= buf;\r\n        assert(b[0..$] == buf[0..6]);\r\n        b += 2;\r\n\r\n        assert(b[0..$] == buf[2..6]);\r\n\r\n        b ~= buf;\r\n        assert(b[0..$] == buf[2..6]);\r\n        b += b.length;\r\n\r\n        assert(b[0..$] == buf[0..6]);\r\n        b += b.length;\r\n\r\n        defaultAllocator.dispose(b);\r\n    }\r\n\r\n    /**\r\n     * After calling it, set $(D_KEYWORD +=) to the length could be\r\n     * written.\r\n     *\r\n     * $(D_PSYMBOL buffer) may return only part of the data. You may need\r\n     * to call it (and set $(D_KEYWORD +=) several times until\r\n     * $(D_PSYMBOL length) is 0). If all the data can be written,\r\n     * maximally 3 calls are required.\r\n     *\r\n     * Returns: A chunk of data buffer.\r\n     */\r\n    @property ubyte[] opIndex() pure nothrow @safe @nogc\r\n    {\r\n        return opSlice(0, length);\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/container/dict.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Trie-based dictionary (associative array) that can use any type as a key\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov, Andrey Penechko, Roman Chistokhodov, ijet\r\n */\r\nmodule dlib.container.dict;\r\n\r\nimport std.stdio;\r\nimport std.traits;\r\nimport std.format;\r\nimport std.string;\r\nimport dlib.core.memory;\r\nimport dlib.container.array;\r\n\r\nsize_t dataSize(T)(T v)\r\n{\r\n    static if (is(T == class) || is(T == interface))\r\n        return (void*).sizeof;\r\n    else\r\n    static if (isArray!T)\r\n        return v.length * (ForeachType!T).sizeof;\r\n    else\r\n        return T.sizeof;\r\n}\r\n\r\nauto byteRange(T)(T v)\r\n{\r\n    struct R\r\n    {\r\n        T value;\r\n        size_t size;\r\n        size_t offset;\r\n\r\n        this(T v)\r\n        {\r\n            value = v;\r\n            size = dataSize(v);\r\n            offset = 0;\r\n        }\r\n\r\n        @property bool empty()\r\n        {\r\n            return (offset >= size);\r\n        }\r\n\r\n        @property ubyte front()\r\n        {\r\n            ubyte* ptr;\r\n            static if (isArray!T) { ptr = (cast(ubyte[])value).ptr;}\r\n            else { ptr = cast(ubyte*)&value; }\r\n            return ptr[offset];\r\n        }\r\n\r\n        void popFront()\r\n        {\r\n            offset++;\r\n        }\r\n    }\r\n\r\n    return R(v);\r\n}\r\n\r\n/**\r\n * Trie-based dictionary (associative array) that can use any type as a key. No hash functions are required.\r\n */\r\nclass Trie(T, K)\r\n{\r\n    T value;\r\n    K key;\r\n    ubyte symbol;\r\n    Array!(Trie!(T, K)) children;\r\n    bool active = false;\r\n    size_t length = 0;\r\n\r\n    this()\r\n    {\r\n    }\r\n\r\n    this(ubyte s)\r\n    {\r\n        symbol = s;\r\n    }\r\n\r\n    /// Set value by key.\r\n    void set(K k, T v)\r\n    {\r\n        Trie!(T, K) current = this;\r\n        foreach(s; byteRange(k))\r\n        {\r\n            bool found = false;\r\n            foreach(c; current.children)\r\n            {\r\n                if (c.symbol == s)\r\n                {\r\n                    current = c;\r\n                    found = true;\r\n                    break;\r\n                }\r\n            }\r\n\r\n            if (!found)\r\n            {\r\n                auto n = New!(Trie!(T, K))(s);\r\n                current.children.append(n);\r\n                current = n;\r\n            }\r\n        }\r\n\r\n        if (current !is this)\r\n        {\r\n            current.value = v;\r\n            current.key = k;\r\n\r\n            if (!current.active)\r\n            {\r\n                current.active = true;\r\n                length++;\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Get value by key. Returns null if the element does not exist in trie.\r\n    T* get(K k)\r\n    {\r\n        Trie!(T, K) current = this;\r\n        foreach(ubyte s; byteRange(k))\r\n        {\r\n            bool found = false;\r\n            foreach(c; current.children)\r\n            {\r\n                if (c.symbol == s)\r\n                {\r\n                    found = true;\r\n                    current = c;\r\n                    break;\r\n                }\r\n            }\r\n\r\n            if (!found)\r\n                return null;\r\n        }\r\n\r\n        if (current !is this)\r\n        {\r\n            if (current.active &&\r\n                current.key == k)\r\n            {\r\n                return &current.value;\r\n            }\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    /// Remove element by key.\r\n    void remove(K k)\r\n    {\r\n        Trie!(T, K) current = this;\r\n        foreach(ubyte s; byteRange(k))\r\n        {\r\n            bool found = false;\r\n            foreach(c; current.children)\r\n            {\r\n                if (c.symbol == s)\r\n                {\r\n                    found = true;\r\n                    current = c;\r\n                    break;\r\n                }\r\n            }\r\n\r\n            if (!found)\r\n                return;\r\n        }\r\n\r\n        if (current !is this)\r\n        {\r\n            if (current.active &&\r\n                current.key == k)\r\n            {\r\n                current.active = false;\r\n                length--;\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Get value by key. It's an error to access non-existing key.\r\n    T opIndex(K k)\r\n    {\r\n        T* v = get(k);\r\n        if (v !is null)\r\n            return *v;\r\n        else\r\n            assert(0, format(\"Non-existing key in Trie.opIndex: %s\", k));\r\n    }\r\n\r\n    /// Set value by key\r\n    T opIndexAssign(T v, K k)\r\n    {\r\n        set(k, v);\r\n        return v;\r\n    }\r\n\r\n    ///\r\n    T* opBinaryRight(string op)(K k) if (op == \"in\")\r\n    {\r\n        return get(k);\r\n    }\r\n\r\n    int opApply(scope int delegate(K, ref T) dg)\r\n    {\r\n        int result = 0;\r\n\r\n        foreach(c; children)\r\n        {\r\n            if (c.active)\r\n                result = dg(c.key, c.value);\r\n\r\n            if (result)\r\n                break;\r\n\r\n            result = c.opApply(dg);\r\n\r\n            if (result)\r\n                break;\r\n        }\r\n\r\n        return result;\r\n    }\r\n\r\n    /// Remove all elements.\r\n    void clear()\r\n    {\r\n        foreach(c; children)\r\n        {\r\n            Delete(c);\r\n        }\r\n\r\n        children.free();\r\n        length = 0;\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        clear();\r\n    }\r\n\r\n    /// Trie must be manually freed when it's no longer needed.\r\n    void free()\r\n    {\r\n        Delete(this);\r\n    }\r\n}\r\n\r\n/// Convenient alias\r\nalias Dict = Trie;\r\n\r\n/// Convenient function for dict creation.\r\nDict!(T, K) dict(T, K)()\r\n{\r\n    return New!(Dict!(T, K))();\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    auto d = dict!(string, string)();\r\n    scope(exit) d.free();\r\n    d[\"Hell\"] = \"No\";\r\n    d[\"Hello\"] = \"World\";\r\n    d[\"Help\"] = \"Me\";\r\n    d[\"Something\"] = \"Else\";\r\n    assert(d[\"Hell\"] == \"No\");\r\n    assert(d[\"Hello\"] == \"World\");\r\n    assert(d[\"Help\"] == \"Me\");\r\n    assert(d[\"Something\"] == \"Else\");\r\n    assert(\"Held\" !in d);\r\n    assert(d.length == 4);\r\n\r\n    string[string] elements;\r\n    foreach(key, value; d)\r\n    {\r\n        elements[key] = value;\r\n    }\r\n    assert(elements[\"Hell\"] == \"No\");\r\n    assert(elements[\"Hello\"] == \"World\");\r\n    assert(elements[\"Help\"] == \"Me\");\r\n    assert(elements[\"Something\"] == \"Else\");\r\n    assert(elements.length == d.length);\r\n\r\n    d[\"Something\"] = \"New\";\r\n    assert(d[\"Something\"] == \"New\");\r\n\r\n    d.remove(\"Hell\");\r\n    assert(d.length == 3);\r\n    assert(d.get(\"Hell\") is null);\r\n\r\n    d.clear();\r\n    assert(d.length == 0);\r\n    assert(\"Hello\" !in d);\r\n    assert(\"Help\" !in d);\r\n    assert(\"Something\" !in d);\r\n\r\n    d[\"Held\"] = \"Fire\";\r\n    assert(d[\"Held\"] == \"Fire\");\r\n\r\n    auto di = dict!(string, int);\r\n    scope(exit) di.free();\r\n    di[0xBEAF] = \"BEAF\";\r\n    di[0xDEADBEAF] = \"DEADBEAF\";\r\n    di[0xDEAD] = \"DEAD\";\r\n    assert(di[0xBEAF] == \"BEAF\");\r\n    assert(di[0xDEADBEAF] == \"DEADBEAF\");\r\n    assert(di[0xDEAD] == \"DEAD\");\r\n}\r\n"
  },
  {
    "path": "dlib/container/linkedlist.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Singly linked list\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov, Andrey Penechko, Roman Chistokhodov, ijet\r\n */\r\nmodule dlib.container.linkedlist;\r\n\r\nimport dlib.core.memory;\r\n\r\n/**\r\n * Element of single linked list.\r\n */\r\nstruct LinkedListElement(T)\r\n{\r\n    LinkedListElement!(T)* next = null;\r\n    T datum;\r\n\r\n    this(LinkedListElement!(T)* n)\r\n    {\r\n        next = n;\r\n        datum = T.init;\r\n    }\r\n}\r\n\r\n/**\r\n * GC-free single linked list implementation.\r\n */\r\nstruct LinkedList(T, bool ordered = true)\r\n{\r\n    ///Head of the list.\r\n    LinkedListElement!(T)* head = null;\r\n    \r\n    ///Tail of the list.\r\n    LinkedListElement!(T)* tail = null;\r\n    \r\n    ///Number of elements in the list.\r\n    size_t length = 0;\r\n\r\n    /**\r\n     * Check if list has no elements.\r\n     */\r\n    @property bool empty()\r\n    {\r\n        return length == 0;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        LinkedList!int list;\r\n        assert(list.empty);\r\n    }\r\n\r\n    /**\r\n     * Remove all elements and free used memory.\r\n     */\r\n    void free()\r\n    {\r\n        LinkedListElement!(T)* element = head;\r\n        while (element !is null)\r\n        {\r\n            auto e = element;\r\n            element = element.next;\r\n            Delete(e);\r\n        }\r\n        head = null;\r\n        tail = null;\r\n        length = 0;\r\n    }\r\n\r\n    /**\r\n     * Iterating over list via foreach.\r\n     */\r\n    int opApply(scope int delegate(size_t, ref T) dg)\r\n    {\r\n        int result = 0;\r\n        uint index = 0;\r\n\r\n        LinkedListElement!(T)* element = head;\r\n        while (element !is null)\r\n        {\r\n            result = dg(index, element.datum);\r\n            if (result)\r\n                break;\r\n            element = element.next;\r\n            index++;\r\n        }\r\n\r\n        return result;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        LinkedList!int list;\r\n        scope(exit) list.free();\r\n\r\n        list.append(1);\r\n        list.append(2);\r\n        list.append(3);\r\n        list.append(4);\r\n\r\n        int[4] values;\r\n\r\n        foreach(size_t i, ref int val; list) {\r\n            values[i] = val;\r\n        }\r\n\r\n        assert(values[] == [1,2,3,4]);\r\n    }\r\n\r\n    /**\r\n     * Iterating over list via foreach.\r\n     */\r\n    int opApply(scope int delegate(ref T) dg)\r\n    {\r\n        int result = 0;\r\n\r\n        LinkedListElement!(T)* element = head;\r\n        while (element !is null)\r\n        {\r\n            result = dg(element.datum);\r\n            if (result)\r\n                break;\r\n            element = element.next;\r\n        }\r\n\r\n        return result;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        LinkedList!int list;\r\n        scope(exit) list.free();\r\n\r\n        list.append(1);\r\n        list.append(2);\r\n        list.append(3);\r\n        list.append(4);\r\n\r\n        int[] values;\r\n\r\n        foreach(ref int val; list) {\r\n            values ~= val;\r\n        }\r\n\r\n        assert(values[] == [1,2,3,4]);\r\n    }\r\n\r\n    /**\r\n     * Appen value v to the end.\r\n     * Returns: Pointer to added list element.\r\n     */\r\n    LinkedListElement!(T)* insertBack(T v)\r\n    {\r\n        length++;\r\n\r\n        if (tail is null)\r\n        {\r\n            tail = New!(LinkedListElement!(T))(null);\r\n            tail.datum = v;\r\n        }\r\n        else\r\n        {\r\n            tail.next = New!(LinkedListElement!(T))(null);\r\n            tail.next.datum = v;\r\n            tail = tail.next;\r\n        }\r\n\r\n        if (head is null) head = tail;\r\n\r\n        return tail;\r\n    }\r\n\r\n    // Insert operator\r\n    auto opCatAssign(T v)\r\n    {\r\n        insertBack(v);\r\n        return this;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        LinkedList!int list;\r\n        scope(exit) list.free();\r\n\r\n        auto element = list.append(13);\r\n        assert(element.datum == 13);\r\n        assert(list.length == 1);\r\n        element = list.append(42);\r\n        assert(element.datum == 42);\r\n        assert(list.length == 2);\r\n    }\r\n\r\n    /**\r\n     * Insert value v after element.\r\n     * Returns: Pointer to inserted element.\r\n     * Note: element must be not null.\r\n     */\r\n    LinkedListElement!(T)* insertAfter(LinkedListElement!(T)* element, T v)\r\n    {\r\n        length++;\r\n        auto newElement = New!(LinkedListElement!(T))(null);\r\n        newElement.datum = v;\r\n        newElement.next = element.next;\r\n        element.next = newElement;\r\n        if (element is tail) tail = newElement;\r\n        return newElement;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        LinkedList!int list;\r\n        scope(exit) list.free();\r\n\r\n        auto first = list.append(1);\r\n        auto last = list.append(2);\r\n        list.insertAfter(first, 3);\r\n\r\n        assert(list.length == 3);\r\n        auto arr = list.toArray();\r\n        assert(arr == [1,3,2]);\r\n        Delete(arr);\r\n    }\r\n\r\n    /**\r\n     * Insert value v at the beginning.\r\n     */\r\n    LinkedListElement!(T)* insertFront(T v)\r\n    {\r\n        length++;\r\n        auto newElement = New!(LinkedListElement!(T))(null);\r\n        newElement.datum = v;\r\n        newElement.next = head;\r\n        head = newElement;\r\n        if (tail is null) {\r\n            tail = head;\r\n        }\r\n        return newElement;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        LinkedList!int list;\r\n        scope(exit) list.free();\r\n\r\n        list.insertBeginning(1);\r\n        list.insertBack(2);\r\n        list.insertBeginning(0);\r\n\r\n        import std.algorithm : equal;\r\n        assert(equal(list.byElement(), [0,1,2]));\r\n    }\r\n\r\n    /**\r\n     * Remove value after element.\r\n     * Note: element must be not null.\r\n     */\r\n    void removeAfter(LinkedListElement!(T)* element)\r\n    {\r\n        length--;\r\n        auto obsolete = element.next;\r\n        if (obsolete !is null)\r\n        {\r\n            if (obsolete is tail) tail = element;\r\n            element.next = obsolete.next;\r\n            Delete(obsolete);\r\n        }\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        LinkedList!int list;\r\n        scope(exit) list.free();\r\n\r\n        auto first = list.insertBack(1);\r\n        auto second = list.insertBack(2);\r\n        auto third = list.insertBack(3);\r\n        list.removeAfter(first);\r\n\r\n        import std.algorithm : equal;\r\n        assert(equal(list.byElement(), [1,3]));\r\n    }\r\n\r\n    /**\r\n     * Remove the first element.\r\n     * Note: list must be non-empty.\r\n     */\r\n    void removeFront()\r\n    {\r\n        length--;\r\n        auto obsolete = head;\r\n        if (obsolete !is null)\r\n        {\r\n            head = obsolete.next;\r\n            Delete(obsolete);\r\n        }\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        LinkedList!int list;\r\n        scope(exit) list.free();\r\n\r\n        list.insertBack(0);\r\n        list.removeFront();\r\n        assert(list.length == 0);\r\n\r\n        list.insertBack(1);\r\n        list.insertBack(2);\r\n        list.insertBack(3);\r\n        list.removeFront();\r\n        assert(list.length == 2);\r\n        import std.algorithm : equal;\r\n        assert(equal(list.byElement(), [2,3]));\r\n    }\r\n\r\n    /**\r\n     * Append other list.\r\n     * Note: Appended list should not be freed. It becomes part of this list.\r\n     */\r\n    void appendList(LinkedList!(T) list)\r\n    {\r\n        length += list.length;\r\n        if (tail !is null) {\r\n            tail.next = list.head;\r\n        }\r\n        if (head is null) {\r\n            head = list.head;\r\n        }\r\n        tail = list.tail;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        LinkedList!int list1;\r\n        scope(exit) list1.free();\r\n        LinkedList!int list2;\r\n        LinkedList!int list3;\r\n\r\n        list2.insertBack(1);\r\n        list2.insertBack(2);\r\n\r\n        list1.appendList(list2);\r\n\r\n        import std.algorithm : equal;\r\n        assert(equal(list1.byElement(), [1,2]));\r\n\r\n        list3.insertBack(3);\r\n        list3.insertBack(4);\r\n        list1.appendList(list3);\r\n\r\n        assert(equal(list1.byElement(), [1,2,3,4]));\r\n    }\r\n\r\n    /**\r\n     * Search for element with value v.\r\n     * Returns: Found element or null if could not find.\r\n     */\r\n    LinkedListElement!(T)* find(T v)\r\n    {\r\n        LinkedListElement!(T)* element = head;\r\n        LinkedListElement!(T)* prevElement = head;\r\n        while (element !is null)\r\n        {\r\n            if (element.datum == v)\r\n            {\r\n                static if (!ordered)\r\n                {\r\n                   /*\r\n                    * Move-to-front heuristic:\r\n                    * Move an element to the beginning of the list once it is found.\r\n                    * This scheme ensures that the most recently used items are also\r\n                    * the quickest to find again.\r\n                    */\r\n                    prevElement.next = element.next;\r\n                    element.next = head;\r\n                    head = element;\r\n                }\r\n\r\n                return element;\r\n            }\r\n\r\n            prevElement = element;\r\n            element = element.next;\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        LinkedList!int list;\r\n        scope(exit) list.free();\r\n\r\n        assert(list.find(42) is null);\r\n\r\n        list.insertBack(13);\r\n        list.insertBack(42);\r\n\r\n        auto first = list.find(13);\r\n        assert(first && first.datum == 13);\r\n\r\n        auto second = list.find(42);\r\n        assert(second && second.datum == 42);\r\n\r\n        assert(list.find(0) is null);\r\n    }\r\n\r\n    /**\r\n     * Convert to array.\r\n     */\r\n    T[] toArray()\r\n    {\r\n        T[] arr = New!(T[])(length);\r\n        foreach(i, v; this)\r\n            arr[i] = v;\r\n        return arr;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        LinkedList!int list;\r\n        scope(exit) list.free();\r\n\r\n        list.insertBack(1);\r\n        list.insertBack(2);\r\n        list.insertBack(3);\r\n\r\n        auto arr = list.toArray();\r\n        assert(arr == [1,2,3]);\r\n        Delete(arr);\r\n    }\r\n\r\n    auto byElement()\r\n    {\r\n        struct ByElement\r\n        {\r\n        private:\r\n            LinkedListElement!(T)* _first;\r\n\r\n        public:\r\n            @property bool empty() {\r\n                return _first is null;\r\n            }\r\n\r\n            @property T front() {\r\n                return _first.datum;\r\n            }\r\n\r\n            void popFront() {\r\n                _first = _first.next;\r\n            }\r\n\r\n            auto save() {\r\n                return this;\r\n            }\r\n        }\r\n\r\n        return ByElement(head);\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        LinkedList!int list;\r\n        scope(exit) list.free();\r\n\r\n        assert(list.byElement().empty);\r\n\r\n        list.insertBack(1);\r\n        list.insertBack(2);\r\n        list.insertBack(3);\r\n\r\n        auto range = list.byElement();\r\n        import std.range: isInputRange;\r\n        import std.algorithm: equal;\r\n        static assert(isInputRange!(typeof(range)));\r\n\r\n        assert(equal(range, [1, 2, 3]));\r\n\r\n        range = list.byElement();\r\n        auto saved = range.save();\r\n        range.popFront();\r\n        assert(equal(range, [2, 3]));\r\n        assert(equal(saved, [1, 2, 3]));\r\n    }\r\n\r\n    // For backward compatibility\r\n    alias append = insertBack;\r\n    alias insertBeginning = insertFront;\r\n    alias removeBeginning = removeFront;\r\n    alias search = find;\r\n}\r\n"
  },
  {
    "path": "dlib/container/mappedlist.d",
    "content": "/*\nCopyright (c) 2018-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/** \n * A list with string-mapped indices.\n *\n * Copyright: Timur Gafarov 2018-2025.\n * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.container.mappedlist;\n\nimport dlib.core.ownership;\nimport dlib.core.memory;\nimport dlib.container.array;\nimport dlib.container.dict;\n\n/**\n * A list with string-mapped indices.\n */\nclass MappedList(T): Owner\n{\n    Array!T data;\n    Dict!(size_t, string) indices;\n\n    this(Owner owner)\n    {\n        super(owner);\n        indices = New!(Dict!(size_t, string))();\n    }\n\n    void set(string name, T val)\n    {\n        data.append(val);\n        indices[name] = data.length - 1;\n    }\n\n    T get(string name)\n    {\n        return data[indices[name]];\n    }\n\n    ~this()\n    {\n        data.free();\n        Delete(indices);\n    }\n}\n"
  },
  {
    "path": "dlib/container/package.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Containers\r\n *\r\n * Description:\r\n * This package implements generic GC-free data containers, such as linked list, \r\n * dynamic array, dictionary, etc. They are based on dlib.core.memory allocators.\r\n * dlib.container is useful when writing applications with manual memory management.\r\n *\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.container;\r\n\r\npublic\r\n{\r\n    import dlib.container.array;\r\n    import dlib.container.buffer;\r\n    import dlib.container.bst;\r\n    import dlib.container.dict;\r\n    import dlib.container.linkedlist;\r\n    import dlib.container.mappedlist;\r\n    import dlib.container.queue;\r\n    import dlib.container.stack;\r\n    import dlib.container.spscqueue;\r\n}\r\n"
  },
  {
    "path": "dlib/container/queue.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Queue (based on linked list)\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov, Andrey Penechko, Roman Chistokhodov\r\n */\r\nmodule dlib.container.queue;\r\n\r\nimport dlib.container.array;\r\n\r\n/**\r\n * Queue implementation based on Array.\r\n */\r\nstruct Queue(T)\r\n{\r\n    private:\r\n    Array!T array;\r\n\r\n    public:\r\n    /**\r\n     * Check if stack has no elements.\r\n     */\r\n    @property bool empty()\r\n    {\r\n        return array.length == 0;\r\n    }\r\n\r\n    /**\r\n     * Add element to queue.\r\n     */\r\n    void enqueue(const(T) v)\r\n    {\r\n        array.insertBack(v);\r\n    }\r\n\r\n    /**\r\n     * Remove element from queue.\r\n     * Returns: Removed element.\r\n     * Throws: Exception if queue is empty.\r\n     */\r\n    T dequeue()\r\n    {\r\n        if (empty)\r\n            throw new Exception(\"Queue!(T): queue is empty\");\r\n\r\n        T res = array[0];\r\n        array.removeFront(1);\r\n        return res;\r\n    }\r\n\r\n    /**\r\n     * Non-throwing version of dequeue.\r\n     * Returns: true on success, false on failure.\r\n     * Element is stored in value.\r\n     */\r\n    bool dequeue(ref T value) nothrow\r\n    {\r\n        if (empty)\r\n            return false;\r\n\r\n        value = array[0];\r\n        array.removeFront(1);\r\n        return true;\r\n    }\r\n\r\n    /**\r\n     * Free memory allocated by Queue.\r\n     */\r\n    void free()\r\n    {\r\n        array.free();\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    import std.exception: assertThrown;\r\n\r\n    Queue!int q;\r\n    assertThrown(q.dequeue());\r\n    assert(q.empty);\r\n\r\n    q.enqueue(50);\r\n    q.enqueue(30);\r\n    q.enqueue(900);\r\n\r\n    int v;\r\n    q.dequeue(v);\r\n    assert(v == 50);\r\n    assert(q.dequeue() == 30);\r\n    assert(q.dequeue() == 900);\r\n    assert(q.empty);\r\n\r\n    q.free();\r\n}\r\n"
  },
  {
    "path": "dlib/container/spscqueue.d",
    "content": "/*\nCopyright (c) 2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Wait-free single-producer single-consumer queue.\n *\n * Copyright: Timur Gafarov 2025\n * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.container.spscqueue;\n\nimport core.atomic;\n\nimport dlib.core.memory;\nimport dlib.core.ownership;\nimport dlib.core.thread;\nimport dlib.container.array;\n\n/**\n * Generic, wait-free single-producer single-consumer queue.\n *\n * Params:\n *   T = element type.\n *   capacity = Maximum number of elements in the queue.\n */\nstruct SPSCQueue(T, size_t capacity)\n{\n    /// Circular buffer.\n    T[capacity] buffer;\n\n    /// Producer write index.\n    shared size_t head = 0;\n    \n    /// Consumer read index.\n    shared size_t tail = 0;\n\n    /// Add an element to the queue. Returns false if full.\n    bool push(T value)\n    {\n        auto next = (head + 1) % capacity;\n        if (next == tail) // queue full\n            return false;\n\n        buffer[head] = value;\n        atomicStore!(MemoryOrder.rel)(head, next);\n        return true;\n    }\n\n    /// Remove an element from the queue. Returns false if empty.\n    bool pop(out T value)\n    {\n        if (tail == atomicLoad!(MemoryOrder.acq)(head))\n            return false; // queue empty\n\n        value = buffer[tail];\n        tail = (tail + 1) % capacity;\n        return true;\n    }\n}\n"
  },
  {
    "path": "dlib/container/stack.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Stack (based on Array)\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov, Andrey Penechko, Roman Chistokhodov\r\n */\r\nmodule dlib.container.stack;\r\n\r\nimport dlib.container.array;\r\n\r\n/**\r\n * Stack implementation based on Array.\r\n */\r\nstruct Stack(T)\r\n{\r\n    private Array!T array;\r\n\r\n    public:\r\n    /**\r\n     * Push element to stack.\r\n     */\r\n    void push(T v)\r\n    {\r\n        array.insertBack(v);\r\n    }\r\n\r\n    /**\r\n     * Pop top element out.\r\n     * Returns: Removed element.\r\n     * Throws: Exception on underflow.\r\n     */\r\n    T pop()\r\n    {\r\n        if (empty)\r\n            throw new Exception(\"Stack!(T): underflow\");\r\n\r\n        T res = array[$-1];\r\n        array.removeBack(1);\r\n        return res;\r\n    }\r\n\r\n    /**\r\n     * Non-throwing version of pop.\r\n     * Returns: true on success, false on failure.\r\n     * Element is stored in value.\r\n     */\r\n    bool pop(ref T value)\r\n    {\r\n        if (empty)\r\n            return false;\r\n\r\n        value = array[$-1];\r\n        array.removeBack(1);\r\n        return true;\r\n    }\r\n\r\n    /**\r\n     * Top stack element.\r\n     * Note: Stack must be non-empty.\r\n     */\r\n    T top()\r\n    {\r\n        return array[$-1];\r\n    }\r\n\r\n    /**\r\n     * Pointer to top stack element.\r\n     * Note: Stack must be non-empty.\r\n     */\r\n    T* topPtr()\r\n    {\r\n        return &array.data[$-1];\r\n    }\r\n\r\n    /**\r\n     * Check if stack has no elements.\r\n     */\r\n    @property bool empty() nothrow\r\n    {\r\n        return array.length == 0;\r\n    }\r\n\r\n    /**\r\n     * Free memory allocated by Stack.\r\n     */\r\n    void free()\r\n    {\r\n        array.free();\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    import std.exception: assertThrown;\r\n\r\n    Stack!int s;\r\n    assertThrown(s.pop());\r\n    s.push(100);\r\n    s.push(3);\r\n    s.push(76);\r\n    assert(s.top() == 76);\r\n    int v;\r\n    s.pop(v);\r\n    assert(v == 76);\r\n    assert(s.pop() == 3);\r\n    assert(s.pop() == 100);\r\n    assert(s.empty);\r\n    s.free();\r\n}\r\n"
  },
  {
    "path": "dlib/core/bitio.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Bit-level manipulations\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.core.bitio;\r\n \r\n/**\r\n * Endianness\r\n */\r\nenum Endian\r\n{\r\n    Little,\r\n    Big\r\n}\r\n\r\n/**\r\n * Returns high 4 bits of a byte\r\n */\r\nT hiNibble(T)(T b)\r\n{\r\n    return ((b >> 4) & 0x0F);\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(hiNibble(0xFE) == 0x0F);\r\n}\r\n\r\n/**\r\n * Returns low 4 bits of a byte\r\n */\r\nT loNibble(T)(T b)\r\n{\r\n    return (b & 0x0F);\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(loNibble(0xFE) == 0x0E);\r\n}\r\n\r\n/**\r\n * Sets bit at position pos in integer b to state\r\n */\r\nT setBit(T)(T b, uint pos, bool state)\r\n{\r\n    if (state)\r\n        return cast(T)(b | (1 << pos));\r\n    else\r\n        return cast(T)(b & ~(1 << pos));\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(setBit(0, 0, true) == 1);\r\n    assert(setBit(1, 0, false) == 0);\r\n}\r\n\r\n/**\r\n * Returns bit at position pos in integer b\r\n */\r\nbool getBit(T)(T b, uint pos)\r\n{\r\n    return ((b & (1 << pos)) != 0);\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(getBit(1, 0) == 1);\r\n}\r\n"
  },
  {
    "path": "dlib/core/compound.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Compile-time sequence (aka tuple) + struct hybrid\r\n *\r\n * Description:\r\n * This template can be used to construct data types on the fly \r\n * and return them from functions, which cannot be done with pure compile-time sequence.\r\n * One possible use case for such types is returning result and error message \r\n * from function instead of throwing an exception.\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 htpps://boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.core.compound;\r\n\r\n/**\r\n * A struct that consists of a compile-time sequence T. Allows square bracket access to the members of a sequence\r\n */\r\nstruct Compound(T...)\r\n{\r\n    T tuple;\r\n    alias tuple this;\r\n}\r\n\r\n/**\r\n * Returns a Compound consisting of args\r\n */\r\nCompound!(T) compound(T...)(T args)\r\n{\r\n    return Compound!(T)(args);\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    auto c = compound(true, 0.5f, \"hello\");\r\n    assert(c[0] == true);\r\n    assert(c[1] == 0.5f);\r\n    assert(c[2] == \"hello\");\r\n}\r\n"
  },
  {
    "path": "dlib/core/memory.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Tools for manual memory management\r\n *\r\n * New/Delete for classes, structs and arrays. It utilizes dlib.memory\r\n * for actual memory allocation, so it is possible to switch allocator that is\r\n * being used. By default, dlib.memory.mallocator.Mallocator is used.\r\n *\r\n * Module includes a simple memory profiler that can be turned on with enableMemoryProfiler\r\n * function. If active, it will store information about every allocation (type and size),\r\n * and will mark those which are leaked (haven't been deleted).\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.core.memory;\r\n\r\nimport std.stdio;\r\nimport std.conv;\r\nimport std.traits;\r\nimport std.datetime;\r\nimport std.algorithm;\r\nimport core.stdc.stdlib;\r\nimport core.exception: onOutOfMemoryError;\r\n\r\nimport dlib.memory;\r\n\r\nprivate __gshared ulong _allocatedMemory = 0;\r\n\r\n/**\r\n * Returns current amount of allocated memory in bytes. This is 0 at program start\r\n */\r\nulong allocatedMemory()\r\n{\r\n    return _allocatedMemory;\r\n}\r\n\r\nprivate __gshared Mallocator _defaultGlobalAllocator;\r\nprivate __gshared Allocator _globalAllocator;\r\n\r\n/**\r\n * Returns current global Allocator that is used by New and Delete\r\n */\r\nAllocator globalAllocator()\r\n{\r\n    if (_globalAllocator is null)\r\n    {\r\n        if (_defaultGlobalAllocator is null)\r\n            _defaultGlobalAllocator = Mallocator.instance;\r\n        _globalAllocator = _defaultGlobalAllocator;\r\n    }\r\n    return _globalAllocator;\r\n}\r\n\r\n/**\r\n * Sets global Allocator that is used by New and Delete\r\n */\r\nvoid globalAllocator(Allocator a)\r\n{\r\n    _globalAllocator = a;\r\n}\r\n\r\nstruct AllocationRecord\r\n{\r\n    string type;\r\n    size_t size;\r\n    string file;\r\n    int line;\r\n    ulong id;\r\n    bool deleted;\r\n}\r\n\r\nprivate\r\n{\r\n    __gshared bool memoryProfilerEnabled = false;\r\n    __gshared AllocationRecord[ulong] records;\r\n    __gshared ulong counter = 0;\r\n\r\n    void addRecord(void* p, string type, size_t size, string file = \"<undefined>\", int line = 0)\r\n    {\r\n        records[cast(ulong)p] = AllocationRecord(type, size, file, line, counter, false);\r\n        counter++;\r\n    }\r\n\r\n    void markDeleted(void* p)\r\n    {\r\n        ulong k = cast(ulong)p - psize;\r\n        records[k].deleted = true;\r\n    }\r\n}\r\n\r\n/**\r\n * Enables or disables memory profiler\r\n */\r\nvoid enableMemoryProfiler(bool mode)\r\n{\r\n    memoryProfilerEnabled = mode;\r\n}\r\n\r\n/**\r\n * Prints full allocation list if memory profiler is enabled, otherwise does nothing\r\n */\r\nvoid printMemoryLog()\r\n{\r\n    writeln(\"----------------------------------------------------\");\r\n    writeln(\"               Memory allocation log                \");\r\n    writeln(\"----------------------------------------------------\");\r\n    auto keys = records.keys;\r\n    sort!((a, b) => records[a].id < records[b].id)(keys);\r\n    foreach(k; keys)\r\n    {\r\n        AllocationRecord r = records[k];\r\n        if (r.deleted)\r\n            writefln(\"         %s - %s byte(s) in %s(%s)\", r.type, r.size, r.file, r.line);\r\n        else\r\n            writefln(\"REMAINS: %s - %s byte(s) in %s(%s)\", r.type, r.size, r.file, r.line);\r\n    }\r\n    writeln(\"----------------------------------------------------\");\r\n    writefln(\"Total amount of allocated memory: %s byte(s)\", _allocatedMemory);\r\n    writeln(\"----------------------------------------------------\");\r\n}\r\n\r\n/**\r\n * Prints leaked allocations if memory profiler is enabled, otherwise does nothing\r\n */\r\nvoid printMemoryLeaks()\r\n{\r\n    writeln(\"----------------------------------------------------\");\r\n    writeln(\"                    Memory leaks                    \");\r\n    writeln(\"----------------------------------------------------\");\r\n    auto keys = records.keys;\r\n    sort!((a, b) => records[a].id < records[b].id)(keys);\r\n    foreach(k; keys)\r\n    {\r\n        AllocationRecord r = records[k];\r\n        if (!r.deleted)\r\n            writefln(\"%s - %s byte(s) in %s(%s)\", r.type, r.size, r.file, r.line);\r\n    }\r\n    writeln(\"----------------------------------------------------\");\r\n    writefln(\"Total amount of leaked memory: %s byte(s)\", _allocatedMemory);\r\n    writeln(\"----------------------------------------------------\");\r\n}\r\n\r\ninterface Freeable\r\n{\r\n    void free();\r\n}\r\n\r\nenum psize = 8;\r\n\r\nstatic if (__VERSION__ >= 2079)\r\n{\r\n    T allocate(T, A...) (A args, string file = __FILE__, int line = __LINE__) if (is(T == class))\r\n    {\r\n        enum size = __traits(classInstanceSize, T);\r\n        void* p = globalAllocator.allocate(size+psize).ptr;\r\n        if (!p)\r\n            onOutOfMemoryError();\r\n        auto memory = p[psize..psize+size];\r\n        *cast(size_t*)p = size;\r\n        _allocatedMemory += size;\r\n        if (memoryProfilerEnabled)\r\n        {\r\n            addRecord(p, T.stringof, size, file, line);\r\n        }\r\n        auto res = emplace!(T, A)(memory, args);\r\n        return res;\r\n    }\r\n\r\n    T* allocate(T, A...) (A args, string file = __FILE__, int line = __LINE__) if (is(T == struct))\r\n    {\r\n        enum size = T.sizeof;\r\n        void* p = globalAllocator.allocate(size+psize).ptr;\r\n        if (!p)\r\n            onOutOfMemoryError();\r\n        auto memory = p[psize..psize+size];\r\n        *cast(size_t*)p = size;\r\n        _allocatedMemory += size;\r\n        if (memoryProfilerEnabled)\r\n        {\r\n            addRecord(p, T.stringof, size, file, line);\r\n        }\r\n        return emplace!(T, A)(memory, args);\r\n    }\r\n\r\n    T allocate(T) (size_t length, string file = __FILE__, int line = __LINE__) if (isArray!T)\r\n    {\r\n        alias AT = ForeachType!T;\r\n        size_t size = length * AT.sizeof;\r\n        auto mem = globalAllocator.allocate(size+psize).ptr;\r\n        if (!mem)\r\n            onOutOfMemoryError();\r\n        T arr = cast(T)mem[psize..psize+size];\r\n        foreach(ref v; arr)\r\n            v = v.init;\r\n        *cast(size_t*)mem = size;\r\n        _allocatedMemory += size;\r\n        if (memoryProfilerEnabled)\r\n        {\r\n            addRecord(mem, T.stringof, size, file, line);\r\n        }\r\n        return arr;\r\n    }\r\n}\r\nelse\r\n{\r\n    T allocate(T, A...) (A args) if (is(T == class))\r\n    {\r\n        enum size = __traits(classInstanceSize, T);\r\n        void* p = globalAllocator.allocate(size+psize).ptr;\r\n        if (!p)\r\n            onOutOfMemoryError();\r\n        auto memory = p[psize..psize+size];\r\n        *cast(size_t*)p = size;\r\n        _allocatedMemory += size;\r\n        if (memoryProfilerEnabled)\r\n        {\r\n            addRecord(p, T.stringof, size);\r\n        }\r\n        auto res = emplace!(T, A)(memory, args);\r\n        return res;\r\n    }\r\n\r\n    T* allocate(T, A...) (A args) if (is(T == struct))\r\n    {\r\n        enum size = T.sizeof;\r\n        void* p = globalAllocator.allocate(size+psize).ptr;\r\n        if (!p)\r\n            onOutOfMemoryError();\r\n        auto memory = p[psize..psize+size];\r\n        *cast(size_t*)p = size;\r\n        _allocatedMemory += size;\r\n        if (memoryProfilerEnabled)\r\n        {\r\n            addRecord(p, T.stringof, size);\r\n        }\r\n        return emplace!(T, A)(memory, args);\r\n    }\r\n\r\n    T allocate(T) (size_t length) if (isArray!T)\r\n    {\r\n        alias AT = ForeachType!T;\r\n        size_t size = length * AT.sizeof;\r\n        auto mem = globalAllocator.allocate(size+psize).ptr;\r\n        if (!mem)\r\n            onOutOfMemoryError();\r\n        T arr = cast(T)mem[psize..psize+size];\r\n        foreach(ref v; arr)\r\n            v = v.init;\r\n        *cast(size_t*)mem = size;\r\n        _allocatedMemory += size;\r\n        if (memoryProfilerEnabled)\r\n        {\r\n            addRecord(mem, T.stringof, size);\r\n        }\r\n        return arr;\r\n    }\r\n}\r\n\r\nvoid deallocate(T)(ref T obj) if (isArray!T)\r\n{\r\n    void* p = cast(void*)obj.ptr;\r\n    size_t size = *cast(size_t*)(p - psize);\r\n    globalAllocator.deallocate((p - psize)[0..size+psize]);\r\n    _allocatedMemory -= size;\r\n    if (memoryProfilerEnabled)\r\n    {\r\n        markDeleted(p);\r\n    }\r\n    obj.length = 0;\r\n}\r\n\r\nvoid deallocate(T)(T obj) if (is(T == class) || is(T == interface))\r\n{\r\n    Object o = cast(Object)obj;\r\n    void* p = cast(void*)o;\r\n    size_t size = *cast(size_t*)(p - psize);\r\n    destroy(obj);\r\n    globalAllocator.deallocate((p - psize)[0..size+psize]);\r\n    _allocatedMemory -= size;\r\n    if (memoryProfilerEnabled)\r\n    {\r\n        markDeleted(p);\r\n    }\r\n}\r\n\r\nvoid deallocate(T)(T* obj)\r\n{\r\n    void* p = cast(void*)obj;\r\n    size_t size = *cast(size_t*)(p - psize);\r\n    destroy(obj);\r\n    globalAllocator.deallocate((p - psize)[0..size+psize]);\r\n    _allocatedMemory -= size;\r\n    if (memoryProfilerEnabled)\r\n    {\r\n        markDeleted(p);\r\n    }\r\n}\r\n\r\n/**\r\n  Creates an object of type T and calls its constructor if necessary.\r\n\r\n  Description:\r\n  This is an equivalent for D's new opetator. It allocates arrays,\r\n  classes and structs on a heap using currently set globalAllocator.\r\n  Arguments to this function are passed to constructor.\r\n\r\n  Examples:\r\n  ----\r\n  MyClass c = New!MyClass(10, 4, 5);\r\n  int[] arr = New!(int[])(100);\r\n  assert(arr.length == 100);\r\n  MyStruct* s = New!MyStruct;\r\n  Delete(c);\r\n  Delete(arr);\r\n  Delete(s);\r\n  ----\r\n */\r\nalias New = allocate;\r\n\r\n/**\r\n  Destroys an object of type T previously created by New and calls\r\n  its destructor if necessary.\r\n\r\n  Examples:\r\n  ----\r\n  MyClass c = New!MyClass(10, 4, 5);\r\n  int[] arr = New!(int[])(100);\r\n  assert(arr.length == 100);\r\n  MyStruct* s = New!MyStruct;\r\n  Delete(c);\r\n  Delete(arr);\r\n  Delete(s);\r\n  ----\r\n */\r\nalias Delete = deallocate;\r\n\r\nunittest\r\n{\r\n    auto mem = allocatedMemory();\r\n    int[] arr = New!(int[])(100);\r\n    assert(arr.length == 100);\r\n    assert(allocatedMemory() - mem == uint.sizeof * 100);\r\n    Delete(arr);\r\n    assert(arr.length == 0);\r\n\r\n    struct Foo { int a; }\r\n    Foo* foo = New!Foo(10);\r\n    assert(foo.a == 10);\r\n    Delete(foo);\r\n}\r\n"
  },
  {
    "path": "dlib/core/mutex.d",
    "content": "/*\nCopyright (c) 2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Cross-platform thread synchronization primitive\n *\n * Copyright: Timur Gafarov 2025.\n * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.core.mutex;\n\nversion(Windows)\n{\n    import core.sys.windows.winbase;\n}\nelse version(Posix)\n{\n    import core.sys.posix.pthread;\n}\n\n/**\n * Mutex structure.\n * Uses CRITICAL_SECTION under Windows, pthread_mutex_t under Posix\n */\nstruct Mutex\n{\n    version(Windows)\n    {\n        CRITICAL_SECTION _mutex;\n    }\n    else version(Posix)\n    {\n        pthread_mutex_t _mutex;\n    }\n    \n    /// Initialize the mutex\n    int init()\n    {\n        version(Windows)\n        {\n            InitializeCriticalSection(&_mutex);\n            return 0;\n        }\n        else version(Posix)\n        {\n            return pthread_mutex_init(&_mutex, null);\n        }\n        else return 0;\n    }\n    \n    /// Enter critical section. If the mutex is already locked, block the thread until it becomes available\n    int lock()\n    {\n        version(Windows)\n        {\n            EnterCriticalSection(&_mutex);\n            return 0;\n        }\n        else version(Posix)\n        {\n            return pthread_mutex_lock(&_mutex);\n        }\n        else return 0;\n    }\n    \n    /// Try to enter critical section. Return immediately if the mutex is already locked\n    int tryLock()\n    {\n        version(Windows)\n        {\n            return !TryEnterCriticalSection(&_mutex);\n        }\n        else version(Posix)\n        {\n            return pthread_mutex_trylock(&_mutex);\n        }\n        else return 0;\n    }\n    \n    /// Leave critical section\n    int unlock()\n    {\n        version(Windows)\n        {\n            LeaveCriticalSection(&_mutex);\n            return 0;\n        }\n        else version(Posix)\n        {\n            return pthread_mutex_unlock(&_mutex);\n        }\n        else return 0;\n    }\n    \n    /// Destroy the mutex\n    int destroy()\n    {\n        version(Windows)\n        {\n            DeleteCriticalSection(&_mutex);\n            return 0;\n        }\n        else version(Posix)\n        {\n            return pthread_mutex_destroy(&_mutex);\n        }\n        else return 0;\n    }\n}\n\n///\nunittest\n{\n    import dlib.core.memory;\n    import dlib.core.thread;\n    \n    int x = 0;\n    Mutex mutex;\n    mutex.init();\n    \n    void add()\n    {\n        mutex.lock();\n        int local = x;\n        local = local + 1;\n        x = local;\n        mutex.unlock();\n    }\n    \n    void sub()\n    {\n        mutex.lock();\n        int local = x;\n        local = local - 1;\n        x = local;\n        mutex.unlock();\n    }\n    \n    Thread[100] threads;\n    \n    foreach(i; 0..threads.length/2)\n    {\n        threads[i] = New!Thread(&add);\n        threads[i].start();\n    }\n    \n    foreach(i; threads.length/2..threads.length)\n    {\n        threads[i] = New!Thread(&sub);\n        threads[i].start();\n    }\n    \n    foreach(t; threads)\n    {\n        t.join();\n    }\n    \n    assert(x == 0);\n}\n"
  },
  {
    "path": "dlib/core/oop.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Prototype-based OOP system for structs.\r\n *\r\n * Description:\r\n * Supports multiple inheritance, parametric polymorphism (struct interfaces),\r\n * interface inheritance\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.core.oop;\r\n\r\nimport std.traits;\r\n\r\n/**\r\n * Inheritance mixin\r\n *\r\n * Description:\r\n * Inserts structs specified by PT type tuple as members and a dispatcher method\r\n * that statically transfers any method calls and member accesses to corresponding\r\n * child struct.\r\n */\r\nmixin template Inherit(PT...)\r\n{\r\n    PT _parent;\r\n    alias _parentTypeTuple = PT;\r\n    alias _parent this;\r\n\r\n    template opDispatch(string s)\r\n    {\r\n        enum e = tupleElemWithMember!(s, PT);\r\n\r\n        static if (hasMethod!(typeof(_parent[e]), s))\r\n        {\r\n            @property auto ref opDispatch(T...)(T params)\r\n            {\r\n                return __traits(getMember, _parent[e], s)(params);\r\n            }\r\n        }\r\n        else\r\n        {\r\n            @property auto ref opDispatch()\r\n            {\r\n                return __traits(getMember, _parent[e], s);\r\n            }\r\n\r\n            @property auto ref opDispatch(T)(T val)\r\n            {\r\n                return __traits(getMember, _parent[e], s) = val;\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    struct Foo\r\n    {\r\n        int x = 100;\r\n        int foo() { return 11; }\r\n    }\r\n\r\n    struct Bar\r\n    {\r\n        int y = 99;\r\n        int bar() { return 22; }\r\n    }\r\n\r\n    struct TestObj\r\n    {\r\n        mixin Inherit!(Foo, Bar);\r\n    }\r\n\r\n    TestObj obj;\r\n\r\n    obj.x *= 2;\r\n    obj.y = 10;\r\n\r\n    assert(obj.x == 200);\r\n    assert(obj.y == 10);\r\n\r\n    assert(obj.foo() == 11);\r\n    assert(obj.bar() == 22);\r\n}\r\n\r\n/**\r\n * Returns index of a tuple element which has specified member (s)\r\n */\r\nsize_t tupleElemWithMember(string s, T...)()\r\n{\r\n    foreach(i, type; T)\r\n    {\r\n        static if (__traits(hasMember, type, s))\r\n            return i;\r\n    }\r\n    assert(0);\r\n}\r\n\r\n/**\r\n * Test if type (T) has specified method (m), local or derived\r\n */\r\nbool hasMethod(T, string m)()\r\n{\r\n    static if (__traits(hasMember, T, m))\r\n    {\r\n        static if (isMethod!(__traits(getMember, T, m)))\r\n        {\r\n            return true;\r\n        }\r\n    }\r\n\r\n    static if (__traits(hasMember, T, \"_parentTypeTuple\"))\r\n    {\r\n        foreach(i, parentType; T._parentTypeTuple)\r\n        {\r\n            static if (hasMethod!(parentType, m))\r\n            {\r\n                return true;\r\n            }\r\n        }\r\n\r\n        return false;\r\n    }\r\n    else\r\n    {\r\n        return false;\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    struct Foo\r\n    {\r\n        int bar() { return 0; }\r\n    }\r\n    \r\n    assert(hasMethod!(Foo, \"bar\")() == true);\r\n    assert(hasMethod!(Foo, \"foo\")() == false);\r\n}\r\n\r\n/**\r\n * Test if F is a method\r\n */\r\ntemplate isMethod(alias F)\r\n{\r\n    enum isMethod =\r\n           isSomeFunction!F &&\r\n          !isFunctionPointer!F &&\r\n          !isDelegate!F;\r\n}\r\n"
  },
  {
    "path": "dlib/core/ownership.d",
    "content": "/*\r\nCopyright (c) 2017-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Class-based object ownership system\r\n *\r\n * Description:\r\n * Object ownership system similar to Delphi's. All classes deriving from Owner\r\n * can store references to objects implementing Owned interface (and other Owner\r\n * objects as well). When an owner is deleted, its owned objects are also deleted.\r\n *\r\n * This module is not compatible with GC-collected objects. It can be used only with\r\n * dlib.core.memory. Using it with objects allocated any other way will cause application to crash.\r\n *\r\n * Copyright: Timur Gafarov 2017-2025.\r\n * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.core.ownership;\r\n\r\nimport dlib.core.memory;\r\nimport dlib.container.array;\r\n\r\n/**\r\n * Interface for objects that can be owned, but not own other objects\r\n */\r\ninterface Owned\r\n{\r\n}\r\n\r\n/**\r\n * Basic owner object class.\r\n * When you delete it, all owned object are automatically deleted\r\n */\r\nclass Owner: Owned\r\n{\r\n    protected Array!Owned ownedObjects;\r\n\r\n    /**\r\n     * Constructor. owner can be null, in this case object won't have an owner.\r\n     * Such objects are called root owners and should be deleted manually.\r\n     */\r\n    this(Owner owner)\r\n    {\r\n        if (owner)\r\n            owner.addOwnedObject(this);\r\n    }\r\n\r\n    /// Add owned object. Usually you don't have to do it explicitly, just pass the owner to constructor\r\n    void addOwnedObject(Owned obj)\r\n    {\r\n        ownedObjects.append(obj);\r\n    }\r\n\r\n    /// Delete owned object without deleting object itself\r\n    void clearOwnedObjects()\r\n    {\r\n        foreach(i, obj; ownedObjects)\r\n            Delete(obj);\r\n        ownedObjects.free();\r\n    }\r\n\r\n    /// Delete particular owned object, if it is there\r\n    void deleteOwnedObject(Owned obj)\r\n    {\r\n        if (ownedObjects.removeFirst(obj))\r\n        {\r\n            Delete(obj);\r\n        }\r\n    }\r\n\r\n    /// Destructor\r\n    ~this()\r\n    {\r\n        clearOwnedObjects();\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    class Test: Owner\r\n    {\r\n        string name;\r\n        static bool[string] available;\r\n\r\n        this(string name, Owner o = null)\r\n        {\r\n            super(o);\r\n            this.name = name;\r\n            available[name] = true;\r\n        }\r\n\r\n        ~this()\r\n        {\r\n            available[name] = false;\r\n        }\r\n    }\r\n\r\n    Test obj1 = New!Test(\"obj1\", null);\r\n    Test obj2 = New!Test(\"obj2\", obj1);\r\n    Test obj3 = New!Test(\"obj3\", obj2);\r\n    obj2.deleteOwnedObject(obj3);\r\n    assert(!Test.available[\"obj3\"]);\r\n    Delete(obj1);\r\n    assert(!Test.available[\"obj2\"]);\r\n    assert(!Test.available[\"obj1\"]);\r\n}\r\n"
  },
  {
    "path": "dlib/core/package.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Provides functionality that is used by all other dlib packages\r\n *\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.core;\r\n\r\npublic\r\n{\r\n    import dlib.core.bitio;\r\n    import dlib.core.compound;\r\n    import dlib.core.memory;\r\n    import dlib.core.mutex;\r\n    import dlib.core.oop;\r\n    import dlib.core.ownership;\r\n    import dlib.core.stream;\r\n    import dlib.core.tuple;\r\n    import dlib.core.thread;\r\n}\r\n"
  },
  {
    "path": "dlib/core/stream.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Martin Cejp\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Binary I/O stream interfaces\r\n *\r\n * Copyright: Martin Cejp 2014-2025.\r\n * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Martin Cejp, Timur Gafarov\r\n */\r\nmodule dlib.core.stream;\r\n\r\nimport std.bitmanip;\r\nimport std.stdint;\r\nimport std.conv;\r\n\r\nimport dlib.core.memory;\r\n\r\nalias StreamPos = uint64_t;\r\nalias StreamSize = uint64_t;\r\nalias StreamOffset = int64_t;\r\n\r\n/// An exception which is throwed on stream errors\r\nclass SeekException : Exception\r\n{\r\n    this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)\r\n    {\r\n        super(msg, file, line, next);\r\n    }\r\n}\r\n\r\n/**\r\n * Seekable stream interface\r\n *\r\n * Description:\r\n * Represents a stream container that knows the size of a stream \r\n * and allows to change byte position within the stream.\r\n */\r\ninterface Seekable\r\n{\r\n    /// Returns current position\r\n    StreamPos getPosition() @property;\r\n    \r\n    /**\r\n     * Attempts to set current position to pos.\r\n     * Returns true on success, false on failure\r\n     */\r\n    bool setPosition(StreamPos pos);\r\n    \r\n    /// Returns the size of a stream in bytes\r\n    StreamSize size();\r\n\r\n    /**\r\n     * Attempts to set current position to pos.\r\n     * Throws SeekException on failure\r\n     */\r\n    final StreamPos position(StreamPos pos)\r\n    {\r\n        if (!setPosition(pos))\r\n            throw new SeekException(\"Cannot set Seekable position to \" ~ pos.to!string);\r\n        return pos;\r\n    }\r\n\r\n    /// ditto\r\n    final StreamPos position()\r\n    {\r\n        return getPosition();\r\n    }\r\n\r\n    /**\r\n     * Relatively changes position.\r\n     * amount defines an offset from the current position (can be negative).\r\n     * Throws SeekException on failure\r\n     */\r\n    final StreamPos seek(StreamOffset amount)\r\n    {\r\n        immutable StreamPos seekTo = getPosition() + amount;\r\n\r\n        if (!setPosition(seekTo))\r\n            throw new SeekException(\"Cannot set Seekable position to \" ~ seekTo.to!string);\r\n\r\n        return seekTo;\r\n    }\r\n}\r\n\r\n/**\r\n * A parent interface for all stream types\r\n */\r\ninterface Stream: Seekable\r\n{\r\n    /// Closes the stream. Closed stream cannot be read or written any more\r\n    void close();\r\n    \r\n    /// Returns true if it is legal to use Seekable functionality on this stream\r\n    bool seekable();\r\n}\r\n\r\n/**\r\n * A stream inteface that allows to read data from it.\r\n * Reading any data implies position advance by corresponding number of bytes.\r\n * Methods shouldn't throw on EOF, may throw on a more serious error\r\n */\r\ninterface InputStream: Stream\r\n{\r\n    /// Returns true if there are any data to read. false means end of the stream.\r\n    bool readable();\r\n    \r\n    /**\r\n     * Attempts to read count bytes from stream and stores them in memory \r\n     * pointing by buffer. Returns number of bytes actually read\r\n     */\r\n    size_t readBytes(void* buffer, size_t count);\r\n\r\n    /**\r\n     * Attempts to fill an array with raw data from stream. \r\n     * Returns true if the array was filled, false otherwise\r\n     */\r\n    final bool fillArray(T)(T[] array)\r\n    {\r\n        immutable size_t len = array.length * T.sizeof;\r\n        return readBytes(array.ptr, len) == len;\r\n    }\r\n\r\n    /**\r\n     * Reads little-endian integer, converts to native-endian \r\n     * and stores in value\r\n     */\r\n    final bool readLE(T)(T* value)\r\n    {\r\n        ubyte[T.sizeof] buffer;\r\n\r\n        if (readBytes(buffer.ptr, buffer.length) != buffer.length)\r\n            return false;\r\n\r\n        *value = littleEndianToNative!T(buffer);\r\n        return true;\r\n    }\r\n\r\n    /**\r\n     * Reads big-endian integer, converts to native-endian\r\n     * and stores in value\r\n     */\r\n    final bool readBE(T)(T* value)\r\n    {\r\n        ubyte[T.sizeof] buffer;\r\n\r\n        if (readBytes(buffer.ptr, buffer.length) != buffer.length)\r\n            return false;\r\n\r\n        *value = bigEndianToNative!T(buffer);\r\n        return true;\r\n    }\r\n}\r\n\r\n/**\r\n * A stream interface that allows to write data into it.\r\n * Methods shouldn't throw on full disk, may throw on a more serious error\r\n */\r\ninterface OutputStream: Stream\r\n{\r\n    /// Implementation-specific method. Usually it writes any unwritten data from output buffer\r\n    void flush();\r\n    \r\n    /// Returns true if stream can be written to, false otherwise\r\n    bool writeable();\r\n    \r\n    /**\r\n     * Attempts to write count bytes from the memory pointed by buffer. \r\n     * Returns number of bytes actually written\r\n     */\r\n    size_t writeBytes(const void* buffer, size_t count);\r\n\r\n    /** \r\n     * Attempts to write an array.\r\n     * Returns true if all elements were written, false otherwise\r\n     */\r\n    final bool writeArray(T)(const T[] array)\r\n    {\r\n        immutable size_t len = array.length * T.sizeof;\r\n        return writeBytes(array.ptr, len) == len;\r\n    }\r\n\r\n    /**\r\n     * Attempts to write a string as zero-terminated. \r\n     * Returns true if entire string was written, false otherwise\r\n     */\r\n    final bool writeStringz(string text)\r\n    {\r\n        ubyte[1] zero = [0];\r\n\r\n        return writeBytes(text.ptr, text.length)\r\n            && writeBytes(zero.ptr, zero.length);\r\n    }\r\n\r\n    /// Writes an integer in little-endian encoding\r\n    final bool writeLE(T)(const T value)\r\n    {\r\n        ubyte[T.sizeof] buffer = nativeToLittleEndian!T(value);\r\n\r\n        return writeBytes(buffer.ptr, buffer.length) == buffer.length;\r\n    }\r\n\r\n    /// Writes an integer in big-endian encoding\r\n    final bool writeBE(T)(const T value)\r\n    {\r\n        ubyte[T.sizeof] buffer = nativeToBigEndian!T(value);\r\n\r\n        return writeBytes(buffer.ptr, buffer.length) == buffer.length;\r\n    }\r\n}\r\n\r\n/**\r\n * A stream that allows both reading and writing data\r\n */\r\ninterface IOStream: InputStream, OutputStream\r\n{\r\n}\r\n\r\n/**\r\n * While input is readable, reads data from input and writes it to output. \r\n * Returns number of bytes read\r\n */\r\nStreamSize copyFromTo(InputStream input, OutputStream output)\r\n{\r\n    ubyte[0x1000] buffer;\r\n    StreamSize total = 0;\r\n\r\n    while (input.readable)\r\n    {\r\n        size_t have = input.readBytes(buffer.ptr, buffer.length);\r\n\r\n        if (have == 0)\r\n            break;\r\n\r\n        output.writeBytes(buffer.ptr, have);\r\n        total += have;\r\n    }\r\n\r\n    return total;\r\n}\r\n\r\n/**\r\n * An InputStream that encapsulates contents of an array\r\n */\r\nclass ArrayStream: InputStream\r\n{\r\n    import std.algorithm;\r\n\r\n    /// Constructor. Initializes stream as empty\r\n    this()\r\n    {\r\n    }\r\n\r\n    /**\r\n     * Constructor. Initializes stream with an array of bytes.\r\n     * size delimits maximum read size\r\n     */\r\n    this(ubyte[] data, size_t size)\r\n    {\r\n        assert(size_ <= data.length);\r\n\r\n        this.size_ = size;\r\n        this.data = data;\r\n    }\r\n\r\n    /**\r\n     * Constructor. Initializes stream with an array of bytes\r\n     */\r\n    this(ubyte[] data)\r\n    {\r\n        this(data, data.length);\r\n    }\r\n\r\n    override void close()\r\n    {\r\n        this.pos = 0;\r\n        this.size_ = 0;\r\n        this.data = null;\r\n    }\r\n\r\n    override bool readable()\r\n    {\r\n        return pos < size_;\r\n    }\r\n\r\n    override size_t readBytes(void* buffer, size_t count)\r\n    {\r\n        import core.stdc.string;\r\n        \r\n        count = min(count, size_ - pos);\r\n        \r\n        // whoops, memcpy out of nowhere, can we do better than that?\r\n        memcpy(buffer, data.ptr + pos, count);\r\n        \r\n        pos += count;\r\n        return count;\r\n    }\r\n\r\n    override bool seekable()\r\n    {\r\n        return true;\r\n    }\r\n\r\n    override StreamPos getPosition()\r\n    {\r\n        return pos;\r\n    }\r\n\r\n    override bool setPosition(StreamPos pos)\r\n    {\r\n        if (pos > size_)\r\n            return false;\r\n        \r\n        this.pos = cast(size_t)pos;\r\n        return true;\r\n    }\r\n\r\n    override StreamSize size()\r\n    {\r\n        return size_;\r\n    }\r\n\r\n    private:\r\n    size_t pos = 0, size_ = 0;\r\n    ubyte[] data; // data.length is capacity\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    ubyte[] arr = [23, 13, 42, 71, 0, 1, 1, 2, 3, 5, 8];\r\n\r\n    auto stream = new ArrayStream(arr);\r\n    assert(stream.size() == arr.length);\r\n\r\n    ubyte[4] buf;\r\n    assert(stream.readBytes(buf.ptr, buf.length) == buf.length);\r\n    assert(buf == [23, 13, 42, 71]);\r\n    assert(stream.getPosition() == buf.length);\r\n\r\n    assert(stream.setPosition(6));\r\n    assert(stream.getPosition == 6);\r\n    assert(stream.readBytes(buf.ptr, buf.length) == buf.length);\r\n    assert(buf == [1, 2, 3, 5]);\r\n\r\n    assert(stream.readBytes(buf.ptr, buf.length) == 1);\r\n    assert(buf[0] == 8);\r\n    assert(!stream.readable);\r\n\r\n    stream.setPosition(1);\r\n    assert(stream.readable);\r\n    stream.seek(4);\r\n    assert(stream.readBytes(buf.ptr, buf.length) == buf.length);\r\n    assert(buf == [1, 1, 2, 3]);\r\n\r\n    assert(stream.setPosition(arr.length));\r\n    assert(!stream.setPosition(arr.length + 1));\r\n\r\n    stream.close();\r\n    assert(!stream.readable);\r\n}\r\n\r\n/**\r\n * An input range that reads data from InputStream by fixed chunks, \r\n * storing them in user-provided array\r\n */\r\nstruct BufferedStreamReader\r\n{\r\n    InputStream stream;\r\n    ubyte[] buffer;\r\n    ubyte[] front;\r\n    \r\n    /// Constructor. Initializes range with a stream and a buffer\r\n    this(InputStream istrm, ubyte[] buffer)\r\n    {\r\n        stream = istrm;\r\n        this.buffer = buffer;\r\n        popFront();\r\n    }\r\n    \r\n    bool empty = false;\r\n    \r\n    void popFront()\r\n    {\r\n        empty = !stream.readable();\r\n        size_t readLen = stream.readBytes(buffer.ptr, buffer.length);\r\n        if (readLen > 0)\r\n            front = buffer[0..readLen];\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/core/thread.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Cross-platform thread class. Supports Windows and Posix\r\n *\r\n * Description:\r\n * This module provides a platform-independent multithreading API.\r\n * For now, it supports Windows and Posix threads (pthreads). The interface\r\n * is greatly inspired by core.thread from Phobos, but dlib.core.thread\r\n * is fully GC-free and can be used with dlib.core.memory allocators.\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.core.thread;\r\n\r\nversion(Windows)\r\n{\r\n    extern(Windows):\r\n    \r\n    alias HANDLE = void*;\r\n\r\n    struct SECURITY_ATTRIBUTES\r\n    {\r\n        uint   nLength;\r\n        void*  lpSecurityDescriptor;\r\n        int    bInheritHandle;\r\n    }\r\n\r\n    alias LPTHREAD_START_ROUTINE = extern(Windows) uint function(const(void)*);\r\n\r\n    void* CreateThread(\r\n        const(SECURITY_ATTRIBUTES)* lpThreadAttributes,\r\n        size_t dwStackSize,\r\n        LPTHREAD_START_ROUTINE lpStartAddress,\r\n        void* lpParameter,\r\n        uint dwCreationFlags,\r\n        uint* lpThreadId\r\n    );\r\n\r\n    uint WaitForMultipleObjects(\r\n        uint nCount,\r\n        const(HANDLE)* lpHandles,\r\n        int bWaitAll,\r\n        uint dwMilliseconds\r\n    );\r\n\r\n    int TerminateThread(\r\n        const(void)* hThread,\r\n        uint dwExitCode\r\n    );\r\n\r\n    int GetExitCodeThread(\r\n        const(void)* hThread,\r\n        uint* lpExitCode\r\n    );\r\n\r\n    int CloseHandle(\r\n        const(void)* hObject\r\n    );\r\n\r\n    enum INFINITE = uint.max;\r\n    enum STILL_ACTIVE = 259;\r\n}\r\n\r\nversion(Posix)\r\n{\r\n    import core.sys.posix.pthread;\r\n}\r\n\r\nversion(Windows)\r\n{\r\n    private extern(Windows) void Sleep(uint msec);\r\n}\r\nelse version(Posix)\r\n{\r\n    import core.stdc.time;\r\n    import core.sys.posix.time;\r\n    import core.sys.posix.signal;\r\n}\r\n\r\n/**\r\n * Base class for creating threads\r\n */\r\nclass Thread\r\n{\r\n    private void function() func;\r\n    private void delegate() dlgt;\r\n    private bool callFunc;\r\n    private bool initialized = false;\r\n\r\n    version(Windows)\r\n    {\r\n        private void* winThread;\r\n    }\r\n\r\n    version(Posix)\r\n    {\r\n        private pthread_t posixThread;\r\n        private bool running = false;\r\n    }\r\n\r\n    /// Constructor. Initializes Thread using a function pointer\r\n    this(void function() func)\r\n    {\r\n        this.func = func;\r\n        callFunc = true;\r\n    }\r\n\r\n    /// Constructor. Initializes Thread using a delegate\r\n    this(void delegate() dlgt)\r\n    {\r\n        this.dlgt = dlgt;\r\n        callFunc = false;\r\n    }\r\n\r\n    /// Destructor\r\n    ~this()\r\n    {\r\n        version(Windows)\r\n        {\r\n            if (winThread)\r\n                CloseHandle(winThread);\r\n        }\r\n\r\n        version(Posix)\r\n        {\r\n            if (initialized)\r\n                pthread_detach(posixThread);\r\n        }\r\n    }\r\n\r\n    /// Starts the thread\r\n    void start()\r\n    {\r\n        version(Windows)\r\n        {\r\n            if (winThread)\r\n                CloseHandle(winThread);\r\n            uint threadId;\r\n            winThread = CreateThread(null, cast(size_t)0, &winThreadFunc, cast(void*)this, cast(uint)0, &threadId);\r\n            assert(winThread !is null);\r\n            initialized = true;\r\n        }\r\n\r\n        version(Posix)\r\n        {\r\n            running = true;\r\n            int error = pthread_create(&posixThread, null, &posixThreadFunc, cast(void*)this);\r\n            assert(error == 0);\r\n            initialized = true;\r\n        }\r\n    }\r\n\r\n    /// Waits for the thread to terminate\r\n    void join()\r\n    {\r\n        version(Windows)\r\n        {\r\n            WaitForMultipleObjects(1u, &winThread, 1, INFINITE);\r\n        }\r\n\r\n        version(Posix)\r\n        {\r\n            pthread_join(posixThread, null);\r\n        }\r\n    }\r\n\r\n    /// Checks if thread is running\r\n    bool isRunning()\r\n    {\r\n        version(Windows)\r\n        {\r\n            uint c = 0;\r\n            GetExitCodeThread(winThread, &c);\r\n            return (c == STILL_ACTIVE);\r\n        }\r\n\r\n        version(Posix)\r\n        {\r\n            return running;\r\n        }\r\n    }\r\n\r\n    /// Stops the thread immediately. This functionality is unsafe, use with care\r\n    void terminate()\r\n    {\r\n        version(Windows)\r\n        {\r\n            TerminateThread(winThread, 1);\r\n        }\r\n\r\n        version(Posix)\r\n        {\r\n            pthread_cancel(posixThread);\r\n            running = false;\r\n        }\r\n    }\r\n\r\n    version(Windows)\r\n    {\r\n        extern(Windows) static uint winThreadFunc(const(void)* lpParam)\r\n        {\r\n            Thread t = cast(Thread)lpParam;\r\n            if (t.callFunc)\r\n                t.func();\r\n            else\r\n                t.dlgt();\r\n            return 0;\r\n        }\r\n    }\r\n\r\n    version(Posix)\r\n    {\r\n        extern(C) static void* posixThreadFunc(void* arg)\r\n        {\r\n            Thread t = cast(Thread)arg;\r\n            if (t.callFunc)\r\n                t.func();\r\n            else\r\n                t.dlgt();\r\n            t.running = false;\r\n            return null;\r\n        }\r\n    }\r\n\r\n    /// Wait for specified amout of milliseconds\r\n    static void sleep(uint msec)\r\n    {\r\n        version(Windows)\r\n        {\r\n            Sleep(msec);\r\n        }\r\n        else version(Posix)\r\n        {\r\n            timespec ts;\r\n            ts.tv_sec = msec / 1000;\r\n            ts.tv_nsec = (msec % 1000) * 1000000;\r\n            nanosleep(&ts, null);\r\n        }\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        Thread.sleep(100);\r\n        assert(true);\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/core/tuple.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Templates that construct tuples of numeric values\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.core.tuple;\r\n\r\n/// Create a tuple\r\ntemplate Tuple(E...)\r\n{\r\n    alias Tuple = E;\r\n}\r\n\r\n/// Yields a tuple of integer literals from 0 to stop\r\ntemplate RangeTuple(int stop)\r\n{\r\n    static if (stop <= 0)\r\n        alias RangeTuple = Tuple!();\r\n    else\r\n        alias RangeTuple = Tuple!(RangeTuple!(stop-1), stop-1);\r\n}\r\n\r\n/// Yields a tuple of integer literals from start to stop\r\ntemplate RangeTuple(int start, int stop)\r\n{\r\n    static if (stop <= start)\r\n        alias RangeTuple = Tuple!();\r\n    else\r\n        alias RangeTuple = Tuple!(RangeTuple!(start, stop-1), stop-1);\r\n}\r\n\r\n/// Yields a tuple of integer literals from start to stop with defined step\r\ntemplate RangeTuple(int start, int stop, int step)\r\n{\r\n    static assert(step != 0, \"RangeTuple: step must be != 0\");\r\n\r\n    static if (step > 0)\r\n    {\r\n        static if (stop <= start)\r\n            alias RangeTuple = Tuple!();\r\n        else\r\n            alias RangeTuple = Tuple!(RangeTuple!(start, stop-step, step), stop-step);\r\n    }\r\n    else\r\n    {\r\n        static if (stop >= start)\r\n            alias RangeTuple = Tuple!();\r\n        else\r\n            alias RangeTuple = Tuple!(RangeTuple!(start, stop-step, step), stop-step);\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/filesystem/delegaterange.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Martin Cejp\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Copyright: Martin Cejp 2014-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Martin Cejp\r\n */\r\nmodule dlib.filesystem.delegaterange;\r\n\r\nprivate import std.range;\r\n\r\n/**\r\n * An input range that enumerates items by obtaining them from a delegate until it returns 0\r\n */\r\nclass DelegateInputRange(T): InputRange!T\r\n{\r\n    bool delegate(out T t) fetch;\r\n    bool have = false;\r\n    T front_;\r\n\r\n    this(bool delegate(out T t) fetch)\r\n    {\r\n        this.fetch = fetch;\r\n    }\r\n\r\n    override bool empty()\r\n    {\r\n        if (!have)\r\n            have = fetch(front_);\r\n\r\n        return !have;\r\n    }\r\n\r\n    override T front()\r\n    {\r\n        return front_;\r\n    }\r\n\r\n    override void popFront()\r\n    {\r\n        have = false;\r\n    }\r\n\r\n    override T moveFront()\r\n    {\r\n        have = false;\r\n        return front_;\r\n    }\r\n\r\n    override int opApply(scope int delegate(T) dg)\r\n    {\r\n        int result = 0;\r\n\r\n        for (size_t i = 0; !empty; i++)\r\n        {\r\n            result = dg(moveFront);\r\n\r\n            if (result != 0)\r\n                break;\r\n        }\r\n\r\n        return result;\r\n    }\r\n\r\n    override int opApply(scope int delegate(size_t, T) dg)\r\n    {\r\n        int result = 0;\r\n\r\n        for (size_t i = 0; !empty; i++)\r\n        {\r\n            result = dg(i, moveFront);\r\n\r\n            if (result != 0)\r\n                break;\r\n        }\r\n\r\n        return result;\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/filesystem/dirrange.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Martin Cejp, Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Copyright: Martin Cejp, Timur Gafarov 2014-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Martin Cejp, Timur Gafarov\r\n */\r\nmodule dlib.filesystem.dirrange;\r\n\r\nimport dlib.filesystem.delegaterange;\r\nimport dlib.filesystem.filesystem;\r\n\r\n/// A DelegateInputRange of DirEntry values\r\nalias DirRange = DelegateInputRange!DirEntry;\r\n"
  },
  {
    "path": "dlib/filesystem/filesystem.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Martin Cejp, Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Copyright: Martin Cejp, Timur Gafarov 2014-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Martin Cejp, Timur Gafarov\r\n */\r\nmodule dlib.filesystem.filesystem;\r\n\r\nimport std.datetime;\r\nimport std.range;\r\nimport std.regex;\r\nimport std.algorithm;\r\nimport std.string;\r\n\r\nimport dlib.core.stream;\r\nimport dlib.filesystem.dirrange;\r\n\r\n/// File size type\r\nalias FileSize = StreamSize;\r\n\r\n/// If a file is readable, FileStat.permissions will have PRead bits set\r\nenum PRead = 0x01;\r\n\r\n/// If a file is writable, FileStat.permissions will have PWrite bits set\r\nenum PWrite = 0x02;\r\n\r\n/// If a file is executable, FileStat.permissions will have PExecute bits set\r\nenum PExecute = 0x04;\r\n\r\n/// Holds general information about a file or directory.\r\nstruct FileStat\r\n{\r\n    /// True if a file is not a directory\r\n    bool isFile;\r\n    \r\n    /// True if a file is a directory\r\n    bool isDirectory;\r\n    \r\n    /// File size. Valid only if isFile is true\r\n    FileSize sizeInBytes;\r\n    \r\n    /// File creation date/time\r\n    SysTime creationTimestamp;\r\n    \r\n    /// File modification date/time\r\n    SysTime modificationTimestamp;\r\n    \r\n    /// Permissions of a file in a given filesystem. Bit combination of PRead, PWrite, PExecute\r\n    int permissions;\r\n}\r\n\r\n/// A filesystem entry - file or directory\r\nstruct DirEntry\r\n{\r\n    /// Entry base name (relative to its containing directory)\r\n    string name;\r\n    \r\n    /// True if an entry is a file\r\n    bool isFile;\r\n    \r\n    /// True if an entry is a directory\r\n    bool isDirectory;\r\n}\r\n\r\n/// A directory in the file system.\r\ninterface Directory\r\n{\r\n    /// \r\n    void close();\r\n\r\n    /// Get directory contents as a range.\r\n    /// This range $(I should) be lazily evaluated when practical.\r\n    /// The entries \".\" and \"..\" are skipped.\r\n    InputRange!DirEntry contents();\r\n}\r\n\r\n/// A filesystem limited to read access.\r\ninterface ReadOnlyFileSystem\r\n{\r\n    /** Get file or directory stats.\r\n        Example:\r\n        ---\r\n        void printFileInfo(ReadOnlyFileSystem fs, string filename)\r\n        {\r\n            FileStat stat;\r\n\r\n            writef(\"'%s'\\t\", filename);\r\n\r\n            if (!fs.stat(filename, stat))\r\n            {\r\n                writeln(\"ERROR\");\r\n                return;\r\n            }\r\n\r\n            if (stat.isFile)\r\n                writefln(\"%u\", stat.sizeInBytes);\r\n            else if (stat.isDirectory)\r\n                writeln(\"DIR\");\r\n\r\n            writefln(\"  created: %s\", to!string(stat.creationTimestamp));\r\n            writefln(\"  modified: %s\", to!string(stat.modificationTimestamp));\r\n        }\r\n        ---\r\n    */\r\n    bool stat(string filename, out FileStat stat);\r\n\r\n    /** Open a file for input.\r\n        Returns: a valid InputStream on success, null on failure\r\n    */\r\n    InputStream openForInput(string filename);\r\n\r\n    /** Open a directory.\r\n    */\r\n    Directory openDir(string path);\r\n}\r\n\r\n/// A file system with read/write access.\r\ninterface FileSystem: ReadOnlyFileSystem\r\n{\r\n    /// File access flags.\r\n    enum\r\n    {\r\n        read = 1,\r\n        write = 2,\r\n    }\r\n\r\n    /// File creation flags.\r\n    enum\r\n    {\r\n        create = 1,\r\n        truncate = 2,\r\n    }\r\n\r\n    /** Open a file for output.\r\n        Returns: a valid OutputStream on success, null on failure\r\n    */\r\n    OutputStream openForOutput(string filename, uint creationFlags);\r\n\r\n    /** Open a file for input & output.\r\n        Returns: a valid IOStream on success, null on failure\r\n    */\r\n    IOStream openForIO(string filename, uint creationFlags);\r\n\r\n    //IOStream openFile(string filename, uint accessFlags, uint creationFlags);\r\n\r\n    /** Create a new directory.\r\n        Returns: true if a new directory was created\r\n        Examples:\r\n        ---\r\n        fs.createDir(\"New Directory\", false);\r\n        fs.createDir(\"nested/directories/are/easy\", true);\r\n        ---\r\n    */\r\n    bool createDir(string path, bool recursive);\r\n\r\n    // BROKEN API. Must define semantics for non-atomic move cases (e.g. moving a file to a different drive)\r\n    //bool move(string path, string newPath);\r\n\r\n    /** Permanently delete a file or directory.\r\n    */\r\n    bool remove(string path, bool recursive);\r\n}\r\n\r\n/**\r\n    Find files in the specified directory\r\n\r\n    Params:\r\n    rofs = filesystem to scan\r\n    baseDir = path to the base directory (if empty, defaults to current working directory)\r\n    recursive = if true, the search will recurse into subdirectories\r\n\r\n    Examples:\r\n    ---\r\n    void listImagesInDirectory(ReadOnlyFileSystem fs, string baseDir = \"\")\r\n    {\r\n        foreach (entry; fs.findFiles(baseDir, true)\r\n                .filter!(entry => entry.isFile)\r\n                .filter!(entry => !matchFirst(entry.name, `.*\\.(gif|jpg|png)$`).empty))\r\n        {\r\n            writefln(\"%s\", entry.name);\r\n        }\r\n    }\r\n    ---\r\n*/\r\nInputRange!DirEntry findFiles(ReadOnlyFileSystem rofs, string baseDir, bool recursive)\r\n{\r\n    // Do some magic so that we don't have to keep our own stack\r\n    import core.thread;\r\n    DirEntry entry;\r\n    \r\n    // findFiles insists on calling us back (it's recursive), but we can trap it in a Fiber\r\n    auto search = new Fiber(delegate void()\r\n    {\r\n        findFiles(rofs, baseDir, recursive, delegate int(ref DirEntry de)\r\n        {\r\n            // save the data (D doesn't allow to yield it directly)\r\n            // and jump outside of the .call() (see below)\r\n            entry = de;\r\n            Fiber.yield();\r\n\r\n            // after resuming, return to findFiles for another round\r\n            return 0;\r\n        });\r\n\r\n        // state becomes TERM after we're resumed after returning the last entry\r\n    });\r\n\r\n    return new DirRange(delegate bool(out DirEntry de)\r\n    {\r\n        // terminated before?\r\n        if (search.state == Fiber.State.TERM)\r\n            return false;\r\n\r\n        // jumps into our search delegate\r\n        search.call();\r\n\r\n        // last entry had been returned last time?\r\n        // (even findFiles didn't know until we returned to it again)\r\n        if (search.state == Fiber.State.TERM)\r\n            return false;\r\n\r\n        de = entry;\r\n        return true;\r\n    });\r\n}\r\n\r\nprivate int findFiles(ReadOnlyFileSystem rofs, string baseDir, bool recursive, int delegate(ref DirEntry entry) dg)\r\n{\r\n    Directory dir = rofs.openDir(baseDir);\r\n    \r\n    if (dir is null)\r\n        return 0;\r\n    \r\n    int result = 0;\r\n    \r\n    try\r\n    {\r\n        foreach (entry; dir.contents)\r\n        {\r\n            if (!baseDir.empty)\r\n                entry.name = baseDir ~ \"/\" ~ entry.name;\r\n            \r\n            result = dg(entry);\r\n            \r\n            if (result != 0)\r\n                return result;\r\n            \r\n            if (recursive && entry.isDirectory)\r\n            {\r\n                result = findFiles(rofs, entry.name, recursive, dg);\r\n            \r\n                if (result != 0)\r\n                    return result;\r\n            }\r\n        }\r\n    }\r\n    \r\n    finally\r\n    {\r\n        dir.close();\r\n    }\r\n    \r\n    return result;\r\n}\r\n"
  },
  {
    "path": "dlib/filesystem/local.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Martin Cejp\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Copyright: Martin Cejp 2014-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Martin Cejp\r\n */\r\nmodule dlib.filesystem.local;\r\n\r\nimport std.array;\r\nimport std.conv;\r\nimport std.datetime;\r\nimport std.path;\r\nimport std.range;\r\nimport std.stdio;\r\nimport std.string;\r\n\r\nimport dlib.core.stream;\r\nimport dlib.filesystem.filesystem;\r\nimport dlib.filesystem.dirrange;\r\n\r\nversion (Posix)\r\n{\r\n    import dlib.filesystem.posix.common;\r\n    import dlib.filesystem.posix.directory;\r\n    import dlib.filesystem.posix.file;\r\n}\r\nelse version (Windows)\r\n{\r\n    import dlib.filesystem.windows.common;\r\n    import dlib.filesystem.windows.directory;\r\n    import dlib.filesystem.windows.file;\r\n}\r\n\r\n// TODO: Should probably check for FILE_ATTRIBUTE_REPARSE_POINT before recursing\r\n\r\n/// LocalFileSystem\r\nclass LocalFileSystem: FileSystem\r\n{\r\n    override InputStream openForInput(string filename)\r\n    {\r\n        return cast(InputStream) openFile(filename, read, 0);\r\n    }\r\n\r\n    override OutputStream openForOutput(string filename, uint creationFlags)\r\n    {\r\n        return cast(OutputStream) openFile(filename, write, creationFlags);\r\n    }\r\n\r\n    override IOStream openForIO(string filename, uint creationFlags)\r\n    {\r\n        return openFile(filename, read | write, creationFlags);\r\n    }\r\n\r\n    override bool createDir(string path, bool recursive)\r\n    {\r\n        import std.algorithm;\r\n\r\n        if (recursive)\r\n        {\r\n            ptrdiff_t index = max(path.lastIndexOf('/'), path.lastIndexOf('\\\\'));\r\n\r\n            if (index != -1)\r\n                createDir(path[0..index], true);\r\n        }\r\n\r\n        version(Posix)\r\n        {\r\n            return mkdir(toStringz(path), access_0755) == 0;\r\n        }\r\n        else version (Windows)\r\n        {\r\n            return CreateDirectoryW(toUTF16z(path), null) != 0;\r\n        }\r\n        else\r\n            throw new Exception(\"Not implemented.\");\r\n    }\r\n\r\n    override Directory openDir(string path)\r\n    {\r\n        version(Posix)\r\n        {\r\n            DIR* d = opendir(!path.empty ? toStringz(path) : \".\");\r\n\r\n            if (d == null)\r\n                return null;\r\n            else\r\n                return new PosixDirectory(this, d, !path.empty ? path ~ \"/\" : \"\");\r\n        }\r\n        else version(Windows)\r\n        {\r\n            string npath = !path.empty ? buildNormalizedPath(path) : \".\";\r\n            DWORD attributes = GetFileAttributesW(toUTF16z(npath));\r\n\r\n            if (attributes == INVALID_FILE_ATTRIBUTES)\r\n                return null;\r\n\r\n            if (attributes & FILE_ATTRIBUTE_DIRECTORY)\r\n                return new WindowsDirectory(this, npath, !path.empty ? path ~ \"/\" : \"\");\r\n            else\r\n                return null;\r\n        }\r\n        else\r\n            throw new Exception(\"Not implemented.\");\r\n    }\r\n\r\n    override bool stat(string path, out FileStat stat_out)\r\n    {\r\n        version(Posix)\r\n        {\r\n            stat_t st;\r\n\r\n            if (stat_(toStringz(path), &st) != 0)\r\n                return false;\r\n\r\n            stat_out.isFile = S_ISREG(st.st_mode);\r\n            stat_out.isDirectory = S_ISDIR(st.st_mode);\r\n\r\n            stat_out.sizeInBytes = st.st_size;\r\n            stat_out.creationTimestamp = SysTime(unixTimeToStdTime(st.st_ctime));\r\n            auto modificationStdTime = unixTimeToStdTime(st.st_mtime);\r\n            static if (is(typeof(st.st_mtimensec)))\r\n            {\r\n                modificationStdTime += st.st_mtimensec / 100;\r\n            }\r\n            stat_out.modificationTimestamp = SysTime(modificationStdTime);\r\n\n            if ((st.st_mode & S_IRUSR) | (st.st_mode & S_IRGRP) | (st.st_mode & S_IROTH))\r\n                stat_out.permissions |= PRead;\r\n            if ((st.st_mode & S_IWUSR) | (st.st_mode & S_IWGRP) | (st.st_mode & S_IWOTH))\r\n                stat_out.permissions |= PWrite;\r\n            if ((st.st_mode & S_IXUSR) | (st.st_mode & S_IXGRP) | (st.st_mode & S_IXOTH))\r\n                stat_out.permissions |= PExecute;\r\n\r\n            return true;\r\n        }\r\n        else version(Windows)\r\n        {\r\n            WIN32_FILE_ATTRIBUTE_DATA data;\r\n            \r\n            auto p = toUTF16z(path);\r\n\r\n            if (!GetFileAttributesExW(p, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &data))\r\n                return false;\r\n\r\n            if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)\r\n                stat_out.isDirectory = true;\r\n            else\r\n                stat_out.isFile = true;\r\n\r\n            stat_out.sizeInBytes = (cast(FileSize) data.nFileSizeHigh << 32) | data.nFileSizeLow;\r\n            stat_out.creationTimestamp = SysTime(FILETIMEToStdTime(&data.ftCreationTime));\r\n            stat_out.modificationTimestamp = SysTime(FILETIMEToStdTime(&data.ftLastWriteTime));\r\n            \r\n            stat_out.permissions = 0;\r\n            \r\n            PACL pacl;\r\n            PSECURITY_DESCRIPTOR secDesc;\r\n            TRUSTEE_W trustee;\r\n            trustee.pMultipleTrustee = null;\r\n            trustee.MultipleTrusteeOperation = MULTIPLE_TRUSTEE_OPERATION.NO_MULTIPLE_TRUSTEE;\r\n            trustee.TrusteeForm = TRUSTEE_FORM.TRUSTEE_IS_NAME;\r\n            trustee.TrusteeType = TRUSTEE_TYPE.TRUSTEE_IS_UNKNOWN;\r\n            trustee.ptstrName = cast(wchar*)\"CURRENT_USER\"w.ptr;\r\n            GetNamedSecurityInfoW(cast(wchar*)p, SE_OBJECT_TYPE.SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, null, null, &pacl, null, &secDesc);\r\n            if (pacl)\r\n            {\r\n                uint access;\r\n                GetEffectiveRightsFromAcl(pacl, &trustee, &access);\r\n                \r\n                if (access & ACTRL_FILE_READ)\r\n                    stat_out.permissions |= PRead;\r\n                if ((access & ACTRL_FILE_WRITE) && !(data.dwFileAttributes & FILE_ATTRIBUTE_READONLY))\r\n                    stat_out.permissions |= PWrite;\r\n                if (access & ACTRL_FILE_EXECUTE)\r\n                    stat_out.permissions |= PExecute;\r\n            }\r\n\r\n            return true;\r\n        }\r\n        else\r\n            throw new Exception(\"Not implemented.\");\r\n    }\r\n\r\n    /*\r\n    override bool move(string path, string newPath)\r\n    {\r\n        // TODO: should we allow newPath to actually be a directory?\r\n\r\n        return rename(toStringz(path), toStringz(newPath)) == 0;\r\n    }\r\n    */\r\n\r\n    override bool remove(string path, bool recursive)\r\n    {\r\n        FileStat stat;\r\n\r\n        if (!this.stat(path, stat))\r\n            return false;\r\n\r\n        return remove(path, stat.isDirectory, recursive);\r\n    }\r\n\r\n   private:\r\n    version(Posix)\r\n    {\r\n        enum access_0644 = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;\r\n        enum access_0755 = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;\r\n    }\r\n\r\n    IOStream openFile(string filename, uint accessFlags, uint creationFlags)\r\n    {\r\n        version(Posix)\r\n        {\r\n            int flags;\r\n\r\n            switch (accessFlags & (read | write))\r\n            {\r\n                case read: flags = O_RDONLY; break;\r\n                case write: flags = O_WRONLY; break;\r\n                case (read | write): flags = O_RDWR; break;\r\n                default: flags = 0; break;\r\n            }\r\n\r\n            if (creationFlags & FileSystem.create)\r\n                flags |= O_CREAT;\r\n\r\n            if (creationFlags & FileSystem.truncate)\r\n                flags |= O_TRUNC;\r\n\r\n            int fd = open(toStringz(filename), flags, access_0644);\r\n\r\n            if (fd < 0)\r\n                return null;\r\n            else\r\n                return new PosixFile(fd, accessFlags);\r\n        }\r\n        else version(Windows)\r\n        {\r\n            DWORD access = 0;\r\n\r\n            if (accessFlags & read)\r\n                access |= GENERIC_READ;\r\n\r\n            if (accessFlags & write)\r\n                access |= GENERIC_WRITE;\r\n\r\n            DWORD creationMode;\r\n\r\n            switch (creationFlags & (create | truncate))\r\n            {\r\n                case 0: creationMode = OPEN_EXISTING; break;\r\n                case create: creationMode = OPEN_ALWAYS; break;\r\n                case truncate: creationMode = TRUNCATE_EXISTING; break;\r\n                case (create | truncate): creationMode = CREATE_ALWAYS; break;\r\n                default: creationMode = OPEN_EXISTING; break;\r\n            }\r\n\r\n            HANDLE file = CreateFileW(toUTF16z(filename), access, FILE_SHARE_READ, null, creationMode,\r\n                FILE_ATTRIBUTE_NORMAL, null);\r\n\r\n            if (file == INVALID_HANDLE_VALUE)\r\n                return null;\r\n            else\r\n                return new WindowsFile(file, accessFlags);\r\n        }\r\n        else\r\n            throw new Exception(\"Not implemented.\");\r\n    }\r\n\r\n    bool remove(string path, bool isDirectory, bool recursive)\r\n    {\r\n        if (isDirectory && recursive)\r\n        {\r\n            // Remove contents\r\n            auto dir = openDir(path);\r\n\r\n            try\r\n            {\r\n                foreach (entry; dir.contents)\r\n                    remove(path ~ \"/\" ~ entry.name, entry.isDirectory, recursive);\r\n            }\r\n            finally\r\n            {\r\n                dir.close();\r\n            }\r\n        }\r\n\r\n        version(Posix)\r\n        {\r\n            if (isDirectory)\r\n                return rmdir(toStringz(path)) == 0;\r\n            else\r\n                return std.stdio.remove(toStringz(path)) == 0;\r\n        }\r\n        else version(Windows)\r\n        {\r\n            if (isDirectory)\r\n                return RemoveDirectoryW(toUTF16z(path)) != 0;\r\n            else\r\n                return DeleteFileW(toUTF16z(path)) != 0;\r\n        }\r\n        else\r\n            throw new Exception(\"Not implemented.\");\r\n    }\r\n}\r\n\r\nprivate ReadOnlyFileSystem rofs;\r\nprivate FileSystem fs;\r\n\r\nstatic this()\r\n{\r\n    // decouple dependency from the rest of this module\r\n    import dlib.filesystem.local;\r\n\r\n    setFileSystem(new LocalFileSystem);\r\n}\r\n\r\nvoid setFileSystem(FileSystem fs_)\r\n{\r\n    rofs = fs_;\r\n    fs = fs_;\r\n}\r\n\r\nvoid setFileSystemReadOnly(ReadOnlyFileSystem rofs_)\r\n{\r\n    rofs = rofs_;\r\n    fs = null;\r\n}\r\n\r\n// ReadOnlyFileSystem\r\n\r\nbool stat(string filename, out FileStat stat)\r\n{\r\n    return rofs.stat(filename, stat);\r\n}\r\n\r\nInputStream openForInput(string filename)\r\n{\r\n    InputStream ins = rofs.openForInput(filename);\r\n\r\n    if (ins is null)\r\n        throw new Exception(\"Failed to open \\\"\" ~ filename ~ \"\\\"\");\r\n\r\n    return ins;\r\n}\r\n\r\nDirectory openDir(string path)\r\n{\r\n    return rofs.openDir(path);\r\n}\r\n\r\nInputRange!DirEntry findFiles(string baseDir, bool recursive)\r\n{\r\n    return dlib.filesystem.filesystem.findFiles(rofs, baseDir, recursive);\r\n}\r\n\r\n// FileSystem\r\n\r\nOutputStream openForOutput(string filename, uint creationFlags = FileSystem.create | FileSystem.truncate)\r\n{\r\n    OutputStream outs = fs.openForOutput(filename, creationFlags);\r\n\r\n    if (outs is null)\r\n        throw new Exception(\"Failed to open \\\"\" ~ filename ~ \"\\\" for writing\");\r\n\r\n    return outs;\r\n}\r\n\r\nIOStream openForIO(string filename, uint creationFlags)\r\n{\r\n    IOStream ios = fs.openForIO(filename, creationFlags);\r\n\r\n    if (ios is null)\r\n        throw new Exception(\"Failed to open \\\"\" ~ filename ~ \"\\\" for writing\");\r\n\r\n    return ios;\r\n}\r\n\r\nbool createDir(string path, bool recursive)\r\n{\r\n    return fs.createDir(path, recursive);\r\n}\r\n\r\n/*\r\nbool move(string path, string newPath)\r\n{\r\n    return fs.move(path, newPath);\r\n}\r\n*/\r\n\r\nbool remove(string path, bool recursive)\r\n{\r\n    return fs.remove(path, recursive);\r\n}\r\n\r\nunittest\r\n{\r\n    // TODO: test >4GiB files\r\n\r\n    import std.algorithm;\r\n    import std.file;\r\n\r\n    alias remove = dlib.filesystem.local.remove;\r\n\r\n    remove(\"tests/test_data\", true);\r\n    assert(openDir(\"tests/test_data\") is null);\r\n\r\n    assert(createDir(\"tests/test_data/main\", true));\r\n\r\n    enum dir = \"tests\";\r\n    auto d = openDir(dir);\r\n\r\n    try\r\n    {\r\n        chdir(dir);\r\n        auto expected = dirEntries(\"\", SpanMode.shallow)\r\n                                  .filter!(e => e.isFile)\r\n                                  .array;\r\n        size_t i;\r\n        chdir(\"..\");\r\n\r\n        foreach (entry; d.contents)\r\n        {\r\n            if (entry.isFile)\r\n            {\r\n                assert(expected[i] == entry.name);\r\n                ++i;\r\n            }\r\n        }\r\n    }\r\n    finally\r\n    {\r\n        d.close();\r\n    }\r\n\r\n    //\r\n    OutputStream outp = openForOutput(\"tests/test_data/main/hello_world.txt\", FileSystem.create | FileSystem.truncate);\r\n    string expected = \"Hello, World!\\n\";\r\n    assert(outp);\r\n\r\n    try\r\n    {\r\n        assert(outp.writeArray(expected));\r\n    }\r\n    finally\r\n    {\r\n        outp.close();\r\n    }\r\n\r\n    //\r\n    InputStream inp = openForInput(\"tests/test_data/main/hello_world.txt\");\r\n    assert(inp);\r\n\r\n    try\r\n    {\r\n        while (inp.readable)\r\n        {\r\n            char[1] buffer;\r\n\r\n            auto have = inp.readBytes(buffer.ptr, buffer.length);\r\n            assert(buffer[0..have] == expected[0..have]);\r\n            expected.popFrontN(have);\r\n        }\r\n    }\r\n    finally\r\n    {\r\n        inp.close();\r\n    }\r\n}\r\n\r\nunittest\r\n{\r\n    import std.algorithm;\r\n    import std.file;\r\n\r\n    auto expected = dirEntries(\"\", SpanMode.depth)\r\n                              .filter!(e => e.isFile)\r\n                              .filter!(e => e.name.baseName.endsWith(\".d\"))\r\n                              .map!(e => e.name.replace(\"\\\\\", \"/\"))\r\n                              .array;\r\n    size_t i;\r\n\r\n    foreach (entry; findFiles(\"\", true)\r\n            .filter!(entry => entry.isFile)\r\n            .filter!(e => e.name.baseName.globMatch(\"*.d\")))\r\n    {\r\n        FileStat stat_;\r\n        assert(stat(entry.name, stat_)); // make sure we're getting the expected path\r\n        assert(expected[i] == entry.name);\r\n        assert(stat_.sizeInBytes == expected[i].getSize());\r\n\r\n        //SysTime modificationTime, accessTime;\r\n        //expected[i].getTimes(accessTime, modificationTime);\r\n        //assert(modificationTime == stat_.modificationTimestamp);\r\n\r\n        ++i;\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/filesystem/package.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Martin Cejp, Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Abstract FS interface and its implementations for Windows and POSIX filesystems\r\n *\r\n * Copyright: Martin Cejp, Timur Gafarov 2014-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Martin Cejp, Timur Gafarov\r\n */\r\nmodule dlib.filesystem;\r\n\r\npublic\r\n{\r\n    import dlib.filesystem.filesystem;\r\n    import dlib.filesystem.local;\r\n    import dlib.filesystem.stdfs;\r\n}\r\n"
  },
  {
    "path": "dlib/filesystem/posix/common.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Martin Cejp\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\nmodule dlib.filesystem.posix.common;\r\n\r\nversion(Posix)\r\n{\r\n    public\r\n    {\r\n        import core.sys.posix.dirent;\r\n        import core.sys.posix.fcntl;\r\n        import core.sys.posix.sys.stat;\r\n        import core.sys.posix.sys.types;\r\n        import core.sys.posix.unistd;\r\n    }\r\n\r\n    // Rename stat to stat_ because of method name collision\r\n    version(linux)\r\n    {\r\n        alias lseek = lseek64;\r\n        alias open = open64;\r\n        alias stat_ = stat64;\r\n    }\r\n    else\r\n    {\r\n        alias stat_ = stat;\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/filesystem/posix/directory.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Martin Cejp\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\nmodule dlib.filesystem.posix.directory;\r\n\r\nversion(Posix)\r\n{\r\n    import std.conv;\r\n    import std.range;\r\n\r\n    import dlib.filesystem.filesystem;\r\n    import dlib.filesystem.dirrange;\r\n    import dlib.filesystem.posix.common;\r\n\r\n    class PosixDirectory: Directory\r\n    {\r\n        FileSystem fs;\r\n        DIR* dir;\r\n        string prefix;\r\n\r\n        this(FileSystem fs, DIR* dir, string prefix)\r\n        {\r\n            this.fs = fs;\r\n            this.dir = dir;\r\n            this.prefix = prefix;\r\n        }\r\n\r\n        ~this()\r\n        {\r\n            close();\r\n        }\r\n\r\n        void close()\r\n        {\r\n            if (dir != null)\r\n            {\r\n                closedir(dir);\r\n                dir = null;\r\n            }\r\n        }\r\n\r\n        InputRange!DirEntry contents()\r\n        {\r\n            if (dir == null)\r\n                return null; // FIXME: throw an error\r\n\r\n            return new DirRange(delegate bool(out DirEntry de)\r\n            {\r\n                dirent entry_buf;\r\n                dirent* entry;\r\n\r\n                for (;;)\r\n                {\r\n                    readdir_r(dir, &entry_buf, &entry);\r\n\r\n                    if (entry == null)\r\n                        return false;\r\n                    else\r\n                    {\r\n                        string name = to!string(cast(const char*) entry.d_name);\r\n\r\n                        if (name == \".\" || name == \"..\")\r\n                            continue;\r\n\r\n                        de.name = name;\r\n                        de.isFile = (entry.d_type & DT_REG) != 0;\r\n                        de.isDirectory = (entry.d_type & DT_DIR) != 0;\r\n\r\n                        return true;\r\n                    }\r\n                }\r\n            });\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/filesystem/posix/file.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Martin Cejp\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\nmodule dlib.filesystem.posix.file;\r\n\r\nversion (Posix)\r\n{\r\n    import dlib.core.stream;\r\n    import dlib.core.memory;\r\n    import dlib.filesystem.filesystem;\r\n    import dlib.filesystem.posix.common;\r\n\r\n    static import core.sys.posix.unistd;\r\n\r\n    class PosixFile: IOStream\r\n    {\r\n        int fd;\r\n        uint accessFlags;\r\n        bool eof = false;\r\n\r\n        this(int fd, uint accessFlags)\r\n        {\r\n            this.fd = fd;\r\n            this.accessFlags = accessFlags;\r\n        }\r\n\r\n        ~this()\r\n        {\r\n            close();\r\n        }\r\n\r\n        override void close()\r\n        {\r\n            if (fd != -1)\r\n            {\r\n                core.sys.posix.unistd.close(fd);\r\n                fd = -1;\r\n            }\r\n        }\r\n\r\n        override bool seekable()\r\n        {\r\n            return true;\r\n        }\r\n\r\n        override StreamPos getPosition()\r\n        {\r\n            import core.sys.posix.stdio;\r\n\r\n            return lseek(fd, 0, SEEK_CUR);\r\n        }\r\n\r\n        override bool setPosition(StreamPos pos)\r\n        {\r\n            import core.sys.posix.stdio;\r\n\r\n            return lseek(fd, pos, SEEK_SET) == pos;\r\n        }\r\n\r\n        override StreamSize size()\r\n        {\r\n            import core.sys.posix.stdio;\r\n\r\n            auto off = lseek(fd, 0, SEEK_CUR);\r\n            auto end = lseek(fd, 0, SEEK_END);\r\n            lseek(fd, off, SEEK_SET);\r\n            return end;\r\n        }\r\n\r\n        override bool readable()\r\n        {\r\n            return fd != -1 && (accessFlags & FileSystem.read) && !eof;\r\n        }\r\n\r\n        override size_t readBytes(void* buffer, size_t count)\r\n        {\r\n            immutable size_t got = core.sys.posix.unistd.read(fd, buffer, count);\r\n\r\n            if (count > got)\r\n                eof = true;\r\n\r\n            return got;\r\n        }\r\n\r\n        override bool writeable()\r\n        {\r\n            return fd != -1 && (accessFlags & FileSystem.write);\r\n        }\r\n\r\n        override size_t writeBytes(const void* buffer, size_t count)\r\n        {\r\n            return core.sys.posix.unistd.write(fd, buffer, count);\r\n        }\r\n\r\n        override void flush()\r\n        {\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/filesystem/stdfs.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * GC-free filesystem\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.filesystem.stdfs;\r\n\r\nimport core.stdc.stdio;\r\nimport std.file;\r\nimport std.string;\r\nimport std.datetime;\r\nimport dlib.core.memory;\r\nimport dlib.core.stream;\r\nimport dlib.container.dict;\r\nimport dlib.container.array;\r\nimport dlib.text.str;\r\nimport dlib.filesystem.filesystem;\r\n\r\nversion(Posix)\r\n{\r\n    import dlib.filesystem.posix.common;\r\n    import dlib.filesystem.stdposixdir;\r\n}\r\nversion(Windows)\r\n{\r\n    import std.stdio;\r\n    import dlib.filesystem.windows.common;\r\n    import dlib.filesystem.stdwindowsdir;\r\n}\r\n\r\nimport dlib.text.utils;\r\nimport dlib.text.utf16;\r\n\r\nversion(Windows)\r\n{\r\n   extern(C) int _wmkdir(const wchar*);\r\n   extern(C) int _wremove(const wchar*);\r\n\r\n   extern(Windows) int RemoveDirectoryW(const wchar*);\r\n}\r\n\r\n/// InputStream that wraps FILE\r\nclass StdInFileStream: InputStream\r\n{\r\n    FILE* file;\r\n    StreamSize _size;\r\n    bool eof;\r\n\r\n    this(FILE* file)\r\n    {\r\n        this.file = file;\r\n\r\n        fseek(file, 0, SEEK_END);\r\n        _size = ftell(file);\r\n        fseek(file, 0, SEEK_SET);\r\n\r\n        eof = false;\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        fclose(file);\r\n    }\r\n\r\n    StreamPos getPosition() @property\r\n    {\r\n        return ftell(file);\r\n    }\r\n\r\n    bool setPosition(StreamPos p)\r\n    {\r\n        import core.stdc.config: c_long;\r\n        return !fseek(file, cast(c_long)p, SEEK_SET);\r\n    }\r\n\r\n    StreamSize size()\r\n    {\r\n        return _size;\r\n    }\r\n\r\n    void close()\r\n    {\r\n        fclose(file);\r\n    }\r\n\r\n    bool seekable()\r\n    {\r\n        return true;\r\n    }\r\n\r\n    bool readable()\r\n    {\r\n        return !eof;\r\n    }\r\n\r\n    size_t readBytes(void* buffer, size_t count)\r\n    {\r\n        auto bytesRead = fread(buffer, 1, count, file);\r\n        if (count > bytesRead)\r\n            eof = true;\r\n        return bytesRead;\r\n    }\r\n}\r\n\r\n/// OutputStream that wraps FILE\r\nclass StdOutFileStream: OutputStream\r\n{\r\n    FILE* file;\r\n    bool _writeable;\r\n\r\n    this(FILE* file)\r\n    {\r\n        this.file = file;\r\n        this._writeable = true;\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        fclose(file);\r\n    }\r\n\r\n    StreamPos getPosition() @property\r\n    {\r\n        return 0;\r\n    }\r\n\r\n    bool setPosition(StreamPos pos)\r\n    {\r\n        return false;\r\n    }\r\n\r\n    StreamSize size()\r\n    {\r\n        return 0;\r\n    }\r\n\r\n    void close()\r\n    {\r\n        fclose(file);\r\n    }\r\n\r\n    bool seekable()\r\n    {\r\n        return false;\r\n    }\r\n\r\n    void flush()\r\n    {\r\n        fflush(file);\r\n    }\r\n\r\n    bool writeable()\r\n    {\r\n        return _writeable;\r\n    }\r\n\r\n    size_t writeBytes(const void* buffer, size_t count)\r\n    {\r\n        size_t res = fwrite(buffer, 1, count, file);\r\n        if (res != count)\r\n            _writeable = false;\r\n        return res;\r\n    }\r\n}\r\n\r\n/// IOStream that wraps FILE\r\nclass StdIOStream: IOStream\r\n{\r\n    FILE* file;\r\n    StreamSize _size;\r\n    bool _eof;\r\n    bool _writeable;\r\n\r\n    this(FILE* file)\r\n    {\r\n        this.file = file;\r\n        this._writeable = true;\r\n\r\n        fseek(file, 0, SEEK_END);\r\n        this._size = ftell(file);\r\n        fseek(file, 0, SEEK_SET);\r\n\r\n        this._eof = false;\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        fclose(file);\r\n    }\r\n\r\n    StreamPos getPosition() @property\r\n    {\r\n        return ftell(file);\r\n    }\r\n\r\n    bool setPosition(StreamPos p)\r\n    {\r\n        import core.stdc.config : c_long;\r\n        return !fseek(file, cast(c_long)p, SEEK_SET);\r\n    }\r\n\r\n    StreamSize size()\r\n    {\r\n        return _size;\r\n    }\r\n\r\n    void close()\r\n    {\r\n        fclose(file);\r\n    }\r\n\r\n    bool seekable()\r\n    {\r\n        return true;\r\n    }\r\n\r\n    bool readable()\r\n    {\r\n        return !_eof;\r\n    }\r\n\r\n    size_t readBytes(void* buffer, size_t count)\r\n    {\r\n        auto bytesRead = fread(buffer, 1, count, file);\r\n        if (count > bytesRead)\r\n            _eof = true;\r\n        return bytesRead;\r\n    }\r\n\r\n    void flush()\r\n    {\r\n        fflush(file);\r\n    }\r\n\r\n    bool writeable()\r\n    {\r\n        return _writeable;\r\n    }\r\n\r\n    size_t writeBytes(const void* buffer, size_t count)\r\n    {\r\n        size_t res = fwrite(buffer, 1, count, file);\r\n        if (res != count)\r\n            _writeable = false;\r\n        return res;\r\n    }\r\n}\r\n\r\n/// FileSystem that wraps libc filesystem functions + some Posix and WinAPI parts for additional functionality\r\nclass StdFileSystem: FileSystem\r\n{\r\n    Dict!(Directory, string) openedDirs;\r\n    Array!string openedDirPaths;\r\n\r\n    this()\r\n    {\r\n        openedDirs = New!(Dict!(Directory, string));\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        foreach(k, v; openedDirs)\r\n            Delete(v);\r\n        Delete(openedDirs);\r\n\r\n        foreach(p; openedDirPaths)\r\n            Delete(p);\r\n        openedDirPaths.free();\r\n    }\r\n\r\n    bool stat(string filename, out FileStat stat)\r\n    {\r\n        if (std.file.exists(filename))\r\n        {\r\n            with(stat)\r\n            {\r\n                version (Posix)\r\n                {\r\n                    stat_t st;\r\n                    String filenamez = String(filename);\r\n                    stat_(filenamez.ptr, &st);\r\n                    filenamez.free();\r\n\r\n                    isFile = S_ISREG(st.st_mode);\r\n                    isDirectory = S_ISDIR(st.st_mode);\r\n                    sizeInBytes = st.st_size;\r\n                    creationTimestamp = SysTime(unixTimeToStdTime(st.st_ctime));\r\n                    auto modificationStdTime = unixTimeToStdTime(st.st_mtime);\r\n                    static if (is(typeof(st.st_mtimensec)))\r\n                    {\r\n                        modificationStdTime += st.st_mtimensec / 100;\r\n                    }\r\n                    modificationTimestamp = SysTime(modificationStdTime);\r\n\r\n                    if ((st.st_mode & S_IRUSR) | (st.st_mode & S_IRGRP) | (st.st_mode & S_IROTH))\r\n                        permissions |= PRead;\r\n                    if ((st.st_mode & S_IWUSR) | (st.st_mode & S_IWGRP) | (st.st_mode & S_IWOTH))\r\n                        permissions |= PWrite;\r\n                    if ((st.st_mode & S_IXUSR) | (st.st_mode & S_IXGRP) | (st.st_mode & S_IXOTH))\r\n                        permissions |= PExecute;\r\n                }\r\n                else version(Windows)\r\n                {\r\n                    wchar[] filename_utf16 = convertUTF8toUTF16(filename, true);\r\n\r\n                    WIN32_FILE_ATTRIBUTE_DATA data;\r\n\r\n                    if (!GetFileAttributesExW(filename_utf16.ptr, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &data))\r\n                        return false;\r\n\r\n                    if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)\r\n                        isDirectory = true;\r\n                    else\r\n                        isFile = true;\r\n\r\n                    sizeInBytes = (cast(FileSize) data.nFileSizeHigh << 32) | data.nFileSizeLow;\r\n                    creationTimestamp = SysTime(FILETIMEToStdTime(&data.ftCreationTime));\r\n                    modificationTimestamp = SysTime(FILETIMEToStdTime(&data.ftLastWriteTime));\r\n\r\n                    permissions = 0;\r\n\r\n                    PACL pacl;\r\n                    PSECURITY_DESCRIPTOR secDesc;\r\n                    TRUSTEE_W trustee;\r\n                    trustee.pMultipleTrustee = null;\r\n                    trustee.MultipleTrusteeOperation = MULTIPLE_TRUSTEE_OPERATION.NO_MULTIPLE_TRUSTEE;\r\n                    trustee.TrusteeForm = TRUSTEE_FORM.TRUSTEE_IS_NAME;\r\n                    trustee.TrusteeType = TRUSTEE_TYPE.TRUSTEE_IS_UNKNOWN;\r\n                    trustee.ptstrName = cast(wchar*)\"CURRENT_USER\"w.ptr;\r\n                    GetNamedSecurityInfoW(filename_utf16.ptr, SE_OBJECT_TYPE.SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, null, null, &pacl, null, &secDesc);\r\n                    if (pacl)\r\n                    {\r\n                        uint access;\r\n                        GetEffectiveRightsFromAcl(pacl, &trustee, &access);\r\n\r\n                        if (access & ACTRL_FILE_READ)\r\n                            permissions |= PRead;\r\n                        if ((access & ACTRL_FILE_WRITE) && !(data.dwFileAttributes & FILE_ATTRIBUTE_READONLY))\r\n                            permissions |= PWrite;\r\n                        if (access & ACTRL_FILE_EXECUTE)\r\n                            permissions |= PExecute;\r\n                    }\r\n\r\n                    Delete(filename_utf16);\r\n                }\r\n                else\r\n                {\r\n                    isFile = std.file.isFile(filename);\r\n                    isDirectory = std.file.isDir(filename);\r\n                    sizeInBytes = std.file.getSize(filename);\r\n                    getTimes(filename,\r\n                        modificationTimestamp,\r\n                        modificationTimestamp);\r\n                }\r\n            }\r\n            return true;\r\n        }\r\n        else\r\n            return false;\r\n    }\r\n\r\n    StdInFileStream openForInput(string filename)\r\n    {\r\n        version(Posix)\r\n        {\r\n            String filenamez = String(filename);\r\n            FILE* file = fopen(filenamez.ptr, \"rb\");\r\n            filenamez.free();\r\n        }\r\n        version(Windows)\r\n        {\r\n            wchar[] filename_utf16 = convertUTF8toUTF16(filename, true);\r\n            wchar[] mode_utf16 = convertUTF8toUTF16(\"rb\", true);\r\n            FILE* file = _wfopen(filename_utf16.ptr, mode_utf16.ptr);\r\n            Delete(filename_utf16);\r\n            Delete(mode_utf16);\r\n        }\r\n        return New!StdInFileStream(file);\r\n    }\r\n\r\n    StdOutFileStream openForOutput(string filename, uint creationFlags = FileSystem.create)\r\n    {\r\n        version(Posix)\r\n        {\r\n            String filenamez = String(filename);\r\n            FILE* file = fopen(filenamez.ptr, \"wb\");\r\n            filenamez.free();\r\n        }\r\n        version(Windows)\r\n        {\r\n            wchar[] filename_utf16 = convertUTF8toUTF16(filename, true);\r\n            wchar[] mode_utf16 = convertUTF8toUTF16(\"wb\", true);\r\n            FILE* file = _wfopen(filename_utf16.ptr, mode_utf16.ptr);\r\n            Delete(filename_utf16);\r\n            Delete(mode_utf16);\r\n        }\r\n        return New!StdOutFileStream(file);\r\n    }\r\n\r\n    StdIOStream openForIO(string filename, uint creationFlags = FileSystem.create)\r\n    {\r\n        version(Posix)\r\n        {\r\n            String filenamez = String(filename);\r\n            FILE* file = fopen(filename.ptr, \"rb+\");\r\n            filenamez.free();\r\n        }\r\n        version(Windows)\r\n        {\r\n            wchar[] filename_utf16 = convertUTF8toUTF16(filename, true);\r\n            wchar[] mode_utf16 = convertUTF8toUTF16(\"rb+\", true);\r\n            FILE* file = _wfopen(filename_utf16.ptr, mode_utf16.ptr);\r\n            Delete(filename_utf16);\r\n            Delete(mode_utf16);\r\n        }\r\n        return New!StdIOStream(file);\r\n    }\r\n\r\n    Directory openDir(string path)\r\n    {\r\n        FileStat ps;\r\n        if (!stat(path, ps))\r\n            return null;\r\n        if (!ps.isDirectory)\r\n            return null;\r\n    \r\n        if (path in openedDirs)\r\n        {\r\n            Directory d = openedDirs[path];\r\n            Delete(d);\r\n        }\r\n\r\n        Directory dir;\r\n\r\n        version(Posix)\r\n        {\r\n            dir = New!StdPosixDirectory(path);\r\n        }\r\n        version(Windows)\r\n        {\r\n            string s = catStr(path, \"\\\\*.*\");\r\n            wchar[] ws = convertUTF8toUTF16(s, true);\r\n            Delete(s);\r\n            dir = New!StdWindowsDirectory(ws.ptr);\r\n        }\r\n\r\n        auto p = New!(char[])(path.length);\r\n        p[] = path[];\r\n        openedDirPaths.append(cast(string)p);\r\n        openedDirs[cast(string)p] = dir;\r\n        \r\n        return dir;\r\n    }\r\n\r\n    bool createDir(string path, bool recursive = true)\r\n    {\r\n        version(Posix)\r\n        {\r\n            String pathz = String(path);\r\n            int res = mkdir(pathz.ptr, 777);\r\n            pathz.free();\r\n            return (res == 0);\r\n        }\r\n        version(Windows)\r\n        {\r\n            wchar[] wp = convertUTF8toUTF16(path, true);\r\n            int res = _wmkdir(wp.ptr);\r\n            Delete(wp);\r\n            return (res == 0);\r\n        }\r\n    }\r\n\r\n    bool remove(string path, bool recursive = true)\r\n    {\r\n        version(Posix)\r\n        {\r\n            String pathz = String(path);\r\n            int res = core.stdc.stdio.remove(pathz.ptr);\r\n            pathz.free();\r\n            return (res == 0);\r\n        }\r\n        version(Windows)\r\n        {\r\n            import std.stdio;\r\n            bool res;\r\n            if (std.file.isDir(path))\r\n            {\r\n                if (recursive)\r\n                foreach(e; openDir(path).contents)\r\n                {\r\n                    string path2 = catStr(path, \"\\\\\");\r\n                    string path3 = catStr(path2, e.name);\r\n                    Delete(path2);\r\n                    this.remove(path3, recursive);\r\n                    Delete(path3);\r\n                }\r\n\r\n                wchar[] wp = convertUTF8toUTF16(path, true);\r\n                res = RemoveDirectoryW(wp.ptr) != 0;\r\n                Delete(wp);\r\n            }\r\n            else\r\n            {\r\n                wchar[] wp = convertUTF8toUTF16(path, true);\r\n                res = _wremove(wp.ptr) == 0;\r\n                Delete(wp);\r\n            }\r\n            return res;\r\n        }\r\n    }\r\n}\r\n\r\n///\r\nversion(Windows)\r\nunittest\r\n{\r\n    StdFileSystem fs = New!StdFileSystem();\r\n    \r\n    string path = \"tests/stdfs\";\r\n    \r\n    FileStat ps;\r\n    if (fs.stat(path, ps))\r\n    {\r\n        fs.remove(path, true);\r\n        assert(fs.openDir(path) is null);\r\n    }\r\n    assert(fs.createDir(path, true));\r\n    \r\n    string filename = \"tests/stdfs/hello_world.txt\";\r\n    \r\n    OutputStream outp = fs.openForOutput(filename, FileSystem.create | FileSystem.truncate);\r\n    assert(outp);\r\n    string data = \"Hello, World!\\n\";\r\n    assert(outp.writeArray(data));\r\n    outp.close();\r\n    \r\n    InputStream inp = fs.openForInput(filename);\r\n    assert(inp);\r\n    string text = readText(inp);\r\n    assert(text == data);\r\n    inp.close();\r\n    \r\n    FileStat s;\r\n    assert(fs.stat(filename, s));\r\n    assert(s.isFile);\r\n    assert(s.sizeInBytes == 14);\r\n}\r\n\r\n/// Reads string from InputStream and stores it in unmanaged memory\r\nstring readText(InputStream istrm)\r\n{\r\n    ubyte[] arr = New!(ubyte[])(cast(size_t)istrm.size);\r\n    istrm.fillArray(arr);\r\n    istrm.setPosition(0);\r\n    return cast(string)arr;\r\n}\r\n\r\n/// Reads struct from InputStream\r\nT readStruct(T)(InputStream istrm) if (is(T == struct))\r\n{\r\n    T res;\r\n    istrm.readBytes(&res, T.sizeof);\r\n    return res;\r\n}\r\n\r\nenum MAX_PATH_LEN = 4096;\r\n\r\nstruct PathBuilder\r\n{\r\n    // TODO: use dlib.text.unmanaged.String instead\r\n    char[MAX_PATH_LEN] str;\r\n    uint length = 0;\r\n\r\n    void append(string s)\r\n    {\r\n        if (length && str[length-1] != '/')\r\n        {\r\n            str[length] = '/';\r\n            length++;\r\n        }\r\n\r\n        str[length..length+s.length] = s[];\r\n        length += s.length;\r\n    }\r\n\r\n    string toString() return\r\n    {\r\n        if (length)\r\n            return cast(string)(str[0..length]);\r\n        else\r\n            return \"\";\r\n    }\r\n}\r\n\r\nstruct RecursiveFileIterator\r\n{\r\n    PathBuilder pb;\r\n    ReadOnlyFileSystem rofs;\r\n    string directory;\r\n    bool rec;\r\n\r\n    this(ReadOnlyFileSystem fs, string dir, bool recursive)\r\n    {\r\n        rofs = fs;\r\n        directory = dir;\r\n        pb.append(dir);\r\n        rec = recursive;\r\n    }\r\n\r\n    int opApply(scope int delegate(string path, ref dlib.filesystem.filesystem.DirEntry) dg)\r\n    {\r\n        int result = 0;\r\n\r\n        if (!rofs)\r\n            return 0;\r\n\r\n        foreach(e; rofs.openDir(directory).contents)\r\n        {\r\n            uint pathPos = pb.length;\r\n            pb.append(e.name);\r\n\r\n            string oldPath = directory;\r\n            directory = pb.toString;\r\n\r\n            result = dg(directory, e);\r\n            if (result)\r\n                break;\r\n\r\n            if (e.isDirectory && rec)\r\n                result = opApply(dg);\r\n\r\n            directory = oldPath;\r\n            pb.length = pathPos;\r\n\r\n            if (result)\r\n                break;\r\n        }\r\n\r\n        return 0;\r\n    }\r\n\r\n    int opApply(scope int delegate(ref dlib.filesystem.filesystem.DirEntry) dg)\r\n    {\r\n        int result = 0;\r\n\r\n        auto dir = rofs.openDir(directory);\r\n\r\n        foreach(e; dir.contents)\r\n        {\r\n            uint pathPos = pb.length;\r\n            pb.append(e.name);\r\n\r\n            string oldPath = directory;\r\n            directory = pb.toString;\r\n\r\n            result = dg(e);\r\n            if (result)\r\n                break;\r\n\r\n            if (e.isDirectory)\r\n                result = opApply(dg);\r\n\r\n            directory = oldPath;\r\n            pb.length = pathPos;\r\n\r\n            if (result)\r\n                break;\r\n        }\r\n\r\n        return 0;\r\n    }\r\n}\r\n\r\n/// Enumerate directory contents, optionally recursive\r\nRecursiveFileIterator traverseDir(ReadOnlyFileSystem rofs, string baseDir, bool recursive)\r\n{\r\n    FileStat s;\r\n    if (!rofs.stat(baseDir, s))\r\n        return RecursiveFileIterator(null, baseDir, recursive);\r\n    else\r\n        return RecursiveFileIterator(rofs, baseDir, recursive);\r\n}\r\n"
  },
  {
    "path": "dlib/filesystem/stdposixdir.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\nmodule dlib.filesystem.stdposixdir;\r\n\r\nimport std.string;\r\nimport std.conv;\r\nimport std.range;\r\nimport core.stdc.string;\r\nversion(Posix)\r\n{\r\n    import core.sys.posix.dirent;\r\n}\r\nimport dlib.core.memory;\r\nimport dlib.filesystem.filesystem;\r\nimport dlib.text.str;\r\n\r\nversion(Posix):\r\n\r\nclass StdPosixDirEntryRange: InputRange!(DirEntry)\r\n{\r\n    DIR* dir;\r\n    dirent* de = null;\r\n    DirEntry currentEntry;\r\n    bool _empty = false;\r\n\r\n    this(DIR* dir)\r\n    {\r\n        this.dir = dir;\r\n        readNextEntry();\r\n    }\r\n\r\n    void readNextEntry()\r\n    {\r\n        de = readdir(dir);\r\n        if (de)\r\n        {\r\n            string name = getFileName(de);\r\n            if (name == \".\" || name == \"..\")\r\n                readNextEntry();\r\n            else\r\n            {\r\n                bool isFile = (de.d_type == DT_REG);\r\n                bool isDir = (de.d_type == DT_DIR);\r\n                currentEntry = DirEntry(getFileName(de), isFile, isDir);\r\n            }\r\n        }\r\n        else\r\n            _empty = true;\r\n    }\r\n\r\n    string getFileName(dirent* d)\r\n    {\r\n        return cast(string)d.d_name[0..strlen(d.d_name.ptr)];\r\n    }\r\n\r\n    void reset()\r\n    {\r\n        rewinddir(dir);\r\n        _empty = false;\r\n    }\r\n\r\n    override DirEntry front()\r\n    {\r\n        return currentEntry;\r\n    }\r\n\r\n    override void popFront()\r\n    {\r\n        readNextEntry();\r\n    }\r\n\r\n    override DirEntry moveFront()\r\n    {\r\n        readNextEntry();\r\n        return currentEntry;\r\n    }\r\n\r\n    override bool empty()\r\n    {\r\n        return _empty;\r\n    }\r\n\r\n    int opApply(scope int delegate(DirEntry) dg)\r\n    {\r\n        while(!_empty)\r\n        {\r\n            dg(currentEntry);\r\n            readNextEntry();\r\n        }\r\n\r\n        return 0;\r\n    }\r\n\r\n    int opApply(scope int delegate(size_t, DirEntry) dg)\r\n    {\r\n        size_t i = 0;\r\n        while(!_empty)\r\n        {\r\n            dg(i, currentEntry);\r\n            readNextEntry();\r\n            i++;\r\n        }\r\n\r\n        return 0;\r\n    }\r\n}\r\n\r\nclass StdPosixDirectory: Directory\r\n{\r\n    DIR* dir;\r\n    StdPosixDirEntryRange drange;\r\n\r\n    this(string path)\r\n    {\r\n        String pathz = String(path);\r\n        dir = opendir(pathz.ptr);\r\n        pathz.free();\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        if (drange)\r\n        {\r\n            Delete(drange);\r\n            drange = null;\r\n        }\r\n\r\n        close();\r\n    }\r\n\r\n    void close()\r\n    {\r\n        if (dir)\r\n        {\r\n            closedir(dir);\r\n            dir = null;\r\n        }\r\n    }\r\n\r\n    StdPosixDirEntryRange contents()\r\n    {\r\n        if (drange)\r\n            drange.reset();\r\n        else\r\n            drange = New!StdPosixDirEntryRange(dir);\r\n        return drange;\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/filesystem/stdwindowsdir.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\nmodule dlib.filesystem.stdwindowsdir;\r\n\r\nimport std.string;\r\nimport std.conv;\r\nimport std.range;\r\nimport core.stdc.string;\r\nimport core.stdc.wchar_;\r\n\r\nversion(Windows)\r\n{\r\n    import core.sys.windows.windows;\r\n}\r\n\r\nimport dlib.core.memory;\r\nimport dlib.filesystem.filesystem;\r\nimport dlib.text.utf16;\r\n\r\nversion(Windows):\r\n\r\nstring unmanagedStrFromCStrW(wchar* cstr)\r\n{\r\n    return cast(string)convertUTF16ztoUTF8(cstr);\r\n}\r\n\r\nclass StdWindowsDirEntryRange: InputRange!(DirEntry)\r\n{\r\n    HANDLE hFind = INVALID_HANDLE_VALUE;\r\n    WIN32_FIND_DATAW findData;\r\n    DirEntry frontEntry;\r\n    bool _empty = false;\r\n    wchar* path;\r\n    bool initialized = false;\r\n\r\n    this(wchar* cwstr)\r\n    {\r\n        this.path = cwstr;\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        if (frontEntry.name.length)\r\n            Delete(frontEntry.name);\r\n\r\n        close();\r\n    }\r\n\r\n    import std.stdio;\r\n\r\n    bool advance()\r\n    {\r\n        bool success = false;\r\n\r\n        if (frontEntry.name.length)\r\n            Delete(frontEntry.name);\r\n\r\n        if (!initialized)\r\n        {\r\n            hFind = FindFirstFileW(path, &findData);\r\n            initialized = true;\r\n            if (hFind != INVALID_HANDLE_VALUE)\r\n                success = true;\r\n\r\n            string name = unmanagedStrFromCStrW(findData.cFileName.ptr);\r\n            if (name == \".\" || name == \"..\")\r\n            {\r\n                success = false;\r\n                Delete(name);\r\n            }\r\n            else\r\n            {\r\n                bool isDir = cast(bool)(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);\r\n                bool isFile = !isDir;\r\n                frontEntry = DirEntry(name, isFile, isDir);\r\n            }\r\n        }\r\n\r\n        if (!success && hFind != INVALID_HANDLE_VALUE)\r\n        {\r\n            string name;\r\n            while(!success)\r\n            {\r\n                auto r = FindNextFileW(hFind, &findData);\r\n                if (!r)\r\n                    break;\r\n\r\n                name = unmanagedStrFromCStrW(findData.cFileName.ptr);\r\n                if (name != \".\" && name != \"..\")\r\n                    success = true;\r\n                else\r\n                    Delete(name);\r\n            }\r\n\r\n            if (success)\r\n            {\r\n                bool isDir = cast(bool)(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);\r\n                bool isFile = !isDir;\r\n                frontEntry = DirEntry(name, isFile, isDir);\r\n            }\r\n        }\r\n\r\n        if (!success)\r\n        {\r\n            FindClose(hFind);\r\n            hFind = INVALID_HANDLE_VALUE;\r\n        }\r\n\r\n        return success;\r\n    }\r\n\r\n    override DirEntry front()\r\n    {\r\n        return frontEntry;\r\n    }\r\n\r\n    override void popFront()\r\n    {\r\n        _empty = !advance();\r\n    }\r\n\r\n    override DirEntry moveFront()\r\n    {\r\n        _empty = !advance();\r\n        return frontEntry;\r\n    }\r\n\r\n    override bool empty()\r\n    {\r\n        return _empty;\r\n    }\r\n\r\n    int opApply(scope int delegate(DirEntry) dg)\r\n    {\r\n        int result = 0;\r\n\r\n        for (size_t i = 0; !empty; i++)\r\n        {\r\n            popFront();\r\n            if (!empty())\r\n                result = dg(frontEntry);\r\n\r\n            if (result != 0)\r\n                break;\r\n        }\r\n\r\n        return result;\r\n    }\r\n\r\n    int opApply(scope int delegate(size_t, DirEntry) dg)\r\n    {\r\n        int result = 0;\r\n\r\n        for (size_t i = 0; !empty; i++)\r\n        {\r\n            popFront();\r\n            if (!empty())\r\n                result = dg(i, frontEntry);\r\n\r\n            if (result != 0)\r\n                break;\r\n        }\r\n\r\n        return result;\r\n    }\r\n\r\n    void reset()\r\n    {\r\n        close();\r\n    }\r\n\r\n    void close()\r\n    {\r\n        if (hFind != INVALID_HANDLE_VALUE)\r\n        {\r\n            FindClose(hFind);\r\n            hFind = INVALID_HANDLE_VALUE;\r\n        }\r\n        initialized = false;\r\n        _empty = false;\r\n    }\r\n}\r\n\r\nclass StdWindowsDirectory: Directory\r\n{\r\n    StdWindowsDirEntryRange drange;\r\n    wchar* path;\r\n\r\n    this(wchar* cwstr)\r\n    {\r\n        path = cwstr;\r\n        drange = New!StdWindowsDirEntryRange(path);\r\n    }\r\n\r\n    void close()\r\n    {\r\n        drange.close();\r\n    }\r\n\r\n    StdWindowsDirEntryRange contents()\r\n    {\r\n        if (drange)\r\n            drange.reset();\r\n        return drange;\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        Delete(drange);\r\n        drange = null;\r\n        Delete(path);\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/filesystem/windows/common.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Martin Cejp\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\nmodule dlib.filesystem.windows.common;\r\n\r\npublic\r\n{\r\n    version(Windows)\r\n    {\r\n        import core.stdc.wchar_;\r\n        import core.sys.windows.windows;\r\n        import core.sys.windows.accctrl;\r\n        import core.sys.windows.aclapi;\r\n        import std.utf;\r\n        import std.windows.syserror;\r\n\r\n        enum DWORD NO_ERROR = 0;\r\n        enum DWORD INVALID_FILE_ATTRIBUTES = cast(DWORD)0xFFFFFFFF;\r\n\r\n        static T wenforce(T)(T cond, string str = null)\r\n        {\r\n            import std.array;\r\n\r\n            if (cond)\r\n                return cond;\r\n\r\n            string err = sysErrorString(GetLastError());\r\n            throw new Exception(!str.empty ? (str ~ \": \" ~ err) : err);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/filesystem/windows/directory.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Martin Cejp\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\nmodule dlib.filesystem.windows.directory;\r\n\r\nversion(Windows)\r\n{\r\n    import dlib.filesystem.filesystem;\r\n    import dlib.filesystem.dirrange;\r\n    import dlib.filesystem.windows.common;\r\n\r\n    import std.conv;\r\n    import std.range;\r\n\r\n    class WindowsDirectory: Directory\r\n    {\r\n        FileSystem fs;\r\n        HANDLE find = INVALID_HANDLE_VALUE;\r\n        string prefix;\r\n\r\n        WIN32_FIND_DATAW entry;\r\n        bool entryValid = false;\r\n\r\n        this(FileSystem fs, string path, string prefix)\r\n        {\r\n            this.fs = fs;\r\n            this.prefix = prefix;\r\n\r\n            find = FindFirstFileW(toUTF16z(path ~ `\\*.*`), &entry);\r\n\r\n            if (find != INVALID_HANDLE_VALUE)\r\n                entryValid = true;\r\n        }\r\n\r\n        ~this()\r\n        {\r\n            close();\r\n        }\r\n\r\n        void close()\r\n        {\r\n            if (find != INVALID_HANDLE_VALUE)\r\n            {\r\n                FindClose(find);\r\n                find = INVALID_HANDLE_VALUE;\r\n            }\r\n        }\r\n\r\n        InputRange!DirEntry contents()\r\n        {\r\n            return new DirRange(delegate bool(out DirEntry de)\r\n            {\r\n                for (;;)\r\n                {\r\n                    WIN32_FIND_DATAW* entry = nextEntry();\r\n\r\n                    if (entry == null)\r\n                        return false;\r\n                    else\r\n                    {\r\n                        size_t len = wcslen(entry.cFileName.ptr);\r\n                        string name = to!string(entry.cFileName[0..len]);\r\n\r\n                        if (name == \".\" || name == \"..\")\r\n                            continue;\r\n\r\n                        de.name = name;\r\n\r\n                        if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)\r\n                            de.isDirectory = true;\r\n                        else\r\n                            de.isFile = true;\r\n\r\n                        return true;\r\n                    }\r\n                }\r\n            });\r\n        }\r\n\r\n        private WIN32_FIND_DATAW* nextEntry()\r\n        {\r\n            if (entryValid)\r\n            {\r\n                entryValid = false;\r\n                return &entry;\r\n            }\r\n\r\n            if (find == INVALID_HANDLE_VALUE || !FindNextFileW(find, &entry))\r\n                return null;\r\n\r\n            return &entry;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/filesystem/windows/file.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Martin Cejp\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\nmodule dlib.filesystem.windows.file;\r\n\r\nversion(Windows)\r\n{\r\n    import dlib.filesystem.filesystem;\r\n    import dlib.filesystem.windows.common;\r\n    import dlib.core.stream;\r\n    import dlib.core.memory;\r\n\r\n    class WindowsFile: IOStream\r\n    {\r\n        HANDLE handle;\r\n        uint accessFlags;\r\n        bool eof = false;\r\n\r\n        this(HANDLE handle, uint accessFlags)\r\n        {\r\n            this.handle = handle;\r\n            this.accessFlags = accessFlags;\r\n        }\r\n\r\n        ~this()\r\n        {\r\n            close();\r\n        }\r\n\r\n        override void close()\r\n        {\r\n            if (handle != INVALID_HANDLE_VALUE)\r\n            {\r\n                CloseHandle(handle);\r\n                handle = INVALID_HANDLE_VALUE;\r\n            }\r\n        }\r\n\r\n        override bool seekable()\r\n        {\r\n            return true;\r\n        }\r\n\r\n        override StreamPos getPosition()\r\n        {\r\n            LONG pos_high = 0;\r\n            LONG pos_low = SetFilePointer(handle, 0, &pos_high, FILE_CURRENT);\r\n\r\n            wenforce(pos_low != INVALID_SET_FILE_POINTER || GetLastError() == NO_ERROR);\r\n\r\n            return cast(StreamPos) pos_high << 32 | pos_low;\r\n        }\r\n\r\n        override bool setPosition(StreamPos pos)\r\n        {\r\n            LONG pos_high = cast(LONG)(pos >> 32);\r\n\r\n            if (SetFilePointer(handle, cast(LONG) pos, &pos_high, FILE_BEGIN) == INVALID_SET_FILE_POINTER\r\n                && GetLastError() != NO_ERROR)\r\n                return false;\r\n            else\r\n                return true;\r\n        }\r\n\r\n        override StreamSize size()\r\n        {\r\n            DWORD size_high;\r\n            DWORD size_low = GetFileSize(handle, &size_high);\r\n\r\n            wenforce(size_low != INVALID_FILE_SIZE || GetLastError() == NO_ERROR);\r\n\r\n            return cast(StreamPos) size_high << 32 | size_low;\r\n        }\r\n\r\n        override bool readable()\r\n        {\r\n            return handle != INVALID_HANDLE_VALUE && (accessFlags & FileSystem.read) && !eof;\r\n        }\r\n\r\n        override size_t readBytes(void* buffer, size_t count)\r\n        {\r\n            // TODO: make sure that count fits in a DWORD\r\n            DWORD dwCount = cast(DWORD) count;\r\n\r\n            DWORD dwGot = void;\r\n            wenforce(ReadFile(handle, buffer, dwCount, &dwGot, null));\r\n\r\n            if (dwCount > dwGot)\r\n                eof = true;\r\n\r\n            return dwGot;\r\n        }\r\n\r\n        override bool writeable()\r\n        {\r\n            return handle != INVALID_HANDLE_VALUE && (accessFlags & FileSystem.write);\r\n        }\r\n\r\n        override size_t writeBytes(const void* buffer, size_t count)\r\n        {\r\n            // TODO: make sure that count fits in a DWORD\r\n            DWORD dwCount = cast(DWORD) count;\r\n\r\n            DWORD dwGot = void;\r\n            wenforce(WriteFile(handle, buffer, dwCount, &dwGot, null));\r\n\r\n            return dwGot;\r\n        }\r\n\r\n        override void flush()\r\n        {\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/geometry/aabb.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.geometry.aabb;\r\n\r\nimport std.math;\r\nimport std.algorithm;\r\nimport dlib.math.vector;\r\nimport dlib.geometry.sphere;\r\nimport dlib.geometry.intersection;\r\n\r\n/// Axis-aligned bounding box\r\nstruct AABB\r\n{\r\n    Vector3f center;\r\n    Vector3f size;\r\n    Vector3f pmin, pmax;\r\n\r\n    this(Vector3f newPosition, Vector3f newSize)\r\n    {\r\n        center = newPosition;\r\n        size = newSize;\r\n\r\n        pmin = center - size;\r\n        pmax = center + size;\r\n    }\r\n\r\n    @property float topHeight()\r\n    {\r\n        return (center.y + size.y);\r\n    }\r\n\r\n    @property float bottomHeight()\r\n    {\r\n        return (center.y - size.y);\r\n    }\r\n\r\n    Vector3f closestPoint(Vector3f point)\r\n    {\r\n        Vector3f closest;\r\n        closest.x = (point.x < pmin.x)? pmin.x : ((point.x > pmax.x)? pmax.x : point.x);\r\n        closest.y = (point.y < pmin.y)? pmin.y : ((point.y > pmax.y)? pmax.y : point.y);\r\n        closest.z = (point.z < pmin.z)? pmin.z : ((point.z > pmax.z)? pmax.z : point.z);\r\n        return closest;\r\n    }\r\n\r\n    bool containsPoint(Vector3f point)\r\n    {\r\n        return !(point.x < pmin.x || point.x > pmax.x ||\r\n                 point.y < pmin.y || point.y > pmax.y ||\r\n                 point.z < pmin.z || point.z > pmax.z);\r\n    }\r\n\r\n    bool intersectsAABB(AABB b)\r\n    {\r\n        Vector3f t = b.center - center;\r\n        return fabs(t.x) <= (size.x + b.size.x) &&\r\n               fabs(t.y) <= (size.y + b.size.y) &&\r\n               fabs(t.z) <= (size.z + b.size.z);\r\n    }\r\n\r\n    deprecated(\"use dlib.geometry.intersection.intrSphereVsAABB instead\")\r\n    bool intersectsSphere(\r\n        Sphere sphere,\r\n        out Vector3f collisionNormal,\r\n        out float penetrationDepth)\r\n    {\r\n        Intersection intr = intrSphereVsAABB(sphere, this);\r\n        collisionNormal = -intr.normal;\r\n        penetrationDepth = intr.penetrationDepth;\r\n        return intr.fact;\r\n    }\r\n\r\n    private bool intersectsRaySlab(\r\n        float slabmin,\r\n        float slabmax,\r\n        float raystart,\r\n        float rayend,\r\n        ref float tbenter,\r\n        ref float tbexit)\r\n    {\r\n        float raydir = rayend - raystart;\r\n\r\n        if (fabs(raydir) < 1.0e-9f)\r\n        {\r\n            if (raystart < slabmin || raystart > slabmax)\r\n                return false;\r\n            else\r\n                return true;\r\n        }\r\n\r\n        float tsenter = (slabmin - raystart) / raydir;\r\n        float tsexit = (slabmax - raystart) / raydir;\r\n\r\n        if (tsenter > tsexit)\r\n        {\r\n            swap(tsenter, tsexit);\r\n        }\r\n\r\n        if (tbenter > tsexit || tsenter > tbexit)\r\n        {\r\n            return false;\r\n        }\r\n        else\r\n        {\r\n            tbenter = max(tbenter, tsenter);\r\n            tbexit = min(tbexit, tsexit);\r\n            return true;\r\n        }\r\n    }\r\n\r\n    bool intersectsSegment(\r\n        Vector3f segStart,\r\n        Vector3f segEnd,\r\n        ref float intersectionTime)\r\n    {\r\n        float tenter = 0.0f, texit = 1.0f;\r\n\r\n        if (!intersectsRaySlab(pmin.x, pmax.x, segStart.x, segEnd.x, tenter, texit))\r\n            return false;\r\n\r\n        if (!intersectsRaySlab(pmin.y, pmax.y, segStart.y, segEnd.y, tenter, texit))\r\n            return false;\r\n\r\n        if (!intersectsRaySlab(pmin.z, pmax.z, segStart.z, segEnd.z, tenter, texit))\r\n            return false;\r\n\r\n        intersectionTime = tenter;\r\n\r\n        return true;\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    import dlib.math.utils;\r\n    \r\n    AABB aabb1 = AABB(Vector3f(0, 0, 0), Vector3f(1, 1, 1));\r\n    AABB aabb2 = AABB(Vector3f(0.5, 0.5, 0.5), Vector3f(1, 1, 1));\r\n    AABB aabb3 = AABB(Vector3f(3, 0, 0), Vector3f(1, 1, 1));\r\n    \r\n    assert(aabb1.containsPoint(Vector3f(0, 0, 0)));\r\n    assert(!aabb3.containsPoint(Vector3f(0, 0, 0)));\r\n    \r\n    assert(aabb1.intersectsAABB(aabb2));\r\n    assert(!aabb1.intersectsAABB(aabb3));\r\n    \r\n    Vector3f segStart = Vector3f(0, 0, 3);\r\n    Vector3f segEnd = Vector3f(0, 0, -3);\r\n    float segLength = distance(segStart, segEnd);\r\n    float t;\r\n    assert(aabb1.intersectsSegment(Vector3f(0, 0, 3), Vector3f(0, 0, -3), t));\r\n    assert(isConsiderZero(t * segLength - 2.0f));\r\n    \r\n    assert(!aabb1.intersectsSegment(Vector3f(5, 0, 3), Vector3f(5, 0, -3), t));\r\n}\r\n\r\n/// Creates AABB from minimum and maximum points\r\nAABB boxFromMinMaxPoints(Vector3f mi, Vector3f ma)\r\n{\r\n    AABB box;\r\n    box.pmin = mi;\r\n    box.pmax = ma;\r\n    box.center = (box.pmax + box.pmin) * 0.5f;\r\n    box.size = box.pmax - box.center;\r\n    box.size.x = abs(box.size.x);\r\n    box.size.y = abs(box.size.y);\r\n    box.size.z = abs(box.size.z);\r\n    return box;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    import dlib.math.utils;\r\n    \r\n    Vector3f pmin = Vector3f(-1, -1, -1);\r\n    Vector3f pmax = Vector3f(1, 1, 1);\r\n    AABB aabb = boxFromMinMaxPoints(pmin, pmax);\r\n    \r\n    assert(isAlmostZero(aabb.center));\r\n    assert(isAlmostZero(aabb.size - Vector3f(1, 1, 1)));\r\n    assert(isAlmostZero(aabb.pmin - pmin));\r\n    assert(isAlmostZero(aabb.pmax - pmax));\r\n    assert(isConsiderZero(aabb.topHeight - 1.0f));\r\n    assert(isConsiderZero(aabb.bottomHeight + 1.0f));\r\n    assert(isAlmostZero(aabb.closestPoint(Vector3f(2.0f, 0.0f, 0.0f)) - Vector3f(1.0f, 0.0f, 0.0f)));\r\n}\r\n"
  },
  {
    "path": "dlib/geometry/frustum.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\n/**\r\n * Copyright: Timur Gafarov 2014-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov, Andrey Penechko, Roman Vlasov\r\n */\r\nmodule dlib.geometry.frustum;\r\n\r\nimport std.math;\r\nimport dlib.math.vector;\r\nimport dlib.math.matrix;\r\nimport dlib.geometry.plane;\r\nimport dlib.geometry.aabb;\r\nimport dlib.geometry.sphere;\r\n\r\n/// Frustum object\r\nstruct Frustum\r\n{\r\n    union\r\n    {\r\n        Plane[6] planes;\r\n        struct\r\n        {\r\n            Plane leftPlane;\r\n            Plane rightPlane;\r\n            Plane bottomPlane;\r\n            Plane topPlane;\r\n            Plane farPlane;\r\n            Plane nearPlane;\r\n        }\r\n    }\r\n\r\n    this(Matrix4x4f mvp)\r\n    {\r\n        fromMVP(mvp);\r\n    }\r\n\r\n    void fromMVP(Matrix4x4f mvp)\r\n    {\r\n        leftPlane.a = mvp[3]  + mvp[0];\r\n        leftPlane.b = mvp[7]  + mvp[4];\r\n        leftPlane.c = mvp[11] + mvp[8];\r\n        leftPlane.d = mvp[15] + mvp[12];\r\n        leftPlane.normalize();\r\n        leftPlane.vectorof = -leftPlane.vectorof;\r\n\r\n        rightPlane.a = mvp[3]  - mvp[0];\r\n        rightPlane.b = mvp[7]  - mvp[4];\r\n        rightPlane.c = mvp[11] - mvp[8];\r\n        rightPlane.d = mvp[15] - mvp[12];\r\n        rightPlane.normalize();\r\n        rightPlane.vectorof = -rightPlane.vectorof;\r\n\r\n        bottomPlane.a = mvp[3]  + mvp[1];\r\n        bottomPlane.b = mvp[7]  + mvp[5];\r\n        bottomPlane.c = mvp[11] + mvp[9];\r\n        bottomPlane.d = mvp[15] + mvp[13];\r\n        bottomPlane.normalize();\r\n        bottomPlane.vectorof = -bottomPlane.vectorof;\r\n\r\n        topPlane.a = mvp[3]  - mvp[1];\r\n        topPlane.b = mvp[7]  - mvp[5];\r\n        topPlane.c = mvp[11] - mvp[9];\r\n        topPlane.d = mvp[15] - mvp[13];\r\n        topPlane.normalize();\r\n        topPlane.vectorof = -topPlane.vectorof;\r\n\r\n        farPlane.a = mvp[3]  - mvp[2];\r\n        farPlane.b = mvp[7]  - mvp[6];\r\n        farPlane.c = mvp[11] - mvp[10];\r\n        farPlane.d = mvp[15] - mvp[14];\r\n        farPlane.normalize();\r\n        farPlane.vectorof = -farPlane.vectorof;\r\n\r\n        nearPlane.a = mvp[3]  + mvp[2];\r\n        nearPlane.b = mvp[7]  + mvp[6];\r\n        nearPlane.c = mvp[11] + mvp[10];\r\n        nearPlane.d = mvp[15] + mvp[14];\r\n        nearPlane.normalize();\r\n        nearPlane.vectorof = -nearPlane.vectorof;\r\n    }\r\n\r\n    bool containsPoint(Vector3f point, bool checkNearPlane = false)\r\n    {\r\n        int res = 0;\r\n\r\n        foreach(i, ref p; planes)\r\n        {\r\n            if (i == 5 && !checkNearPlane)\r\n                break;\r\n\r\n            if (p.distance(point) <= 0.0f)\r\n                res++;\r\n        }\r\n\r\n        return (res == (checkNearPlane? 6 : 5));\r\n    }\r\n\r\n    bool intersectsSphere(Sphere sphere)\r\n    {\r\n        float d;\r\n\r\n        foreach(i, ref p; planes)\r\n        {\r\n            d = p.distance(sphere.center);\r\n\r\n            if (d > sphere.radius)\r\n                return false;\r\n        }\r\n\r\n        return true;\r\n    }\r\n\r\n    bool intersectsAABB(\r\n        AABB aabb,\r\n        bool checkBoundariesOnly = false,\r\n        bool checkNearPlane = true)\r\n    {\r\n        bool result = !checkBoundariesOnly; // Inside\r\n\r\n        foreach (i, ref plane; planes)\r\n        {\r\n            if (i == 5 && !checkNearPlane)\r\n                break;\r\n\r\n            float d = dot(aabb.center, -plane.normal);\r\n\r\n            float r = aabb.size.x * abs(plane.normal.x) +\r\n                      aabb.size.y * abs(plane.normal.y) +\r\n                      aabb.size.z * abs(plane.normal.z);\r\n\r\n            float d_p_r = d + r;\r\n            float d_m_r = d - r;\r\n\r\n            if (d_p_r < plane.d)\r\n            {\r\n                result = false; // Outside\r\n                break;\r\n            }\r\n            else if(d_m_r < plane.d)\r\n                result = true; // Intersect\r\n        }\r\n\r\n        return result;\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    import dlib.math.transformation;\r\n    \r\n    Matrix4x4f mvp = perspectiveMatrix(60.0f, 16.0f/9.0f, 0.1f, 1000.0f);\r\n    Frustum f = Frustum(mvp);\r\n    \r\n    assert(f.containsPoint(Vector3f(0, 0, -10)));\r\n    \r\n    Sphere s = Sphere(Vector3f(0, 0, 10), 1);\r\n    assert(!f.intersectsSphere(s));\r\n    \r\n    AABB aabb = AABB(Vector3f(0, 0, 0), Vector3f(1, 1, 1));\r\n    assert(f.intersectsAABB(aabb));\r\n}\r\n"
  },
  {
    "path": "dlib/geometry/intersection.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.geometry.intersection;\r\n\r\nimport std.math;\r\nimport dlib.math.vector;\r\nimport dlib.math.utils;\r\nimport dlib.math.transformation;\r\nimport dlib.geometry.aabb;\r\nimport dlib.geometry.obb;\r\nimport dlib.geometry.plane;\r\nimport dlib.geometry.sphere;\r\nimport dlib.geometry.triangle;\r\n\r\n/// Stores intersection data\r\nstruct Intersection\r\n{\r\n    bool fact = false;\r\n    Vector3f point;\r\n    Vector3f normal;\r\n    float penetrationDepth;\r\n}\r\n\r\n/// Checks two spheres for intersection\r\nIntersection intrSphereVsSphere(ref Sphere sphere1, ref Sphere sphere2)\r\n{\r\n    Intersection res;\r\n    res.fact = false;\r\n\r\n    float d = distance(sphere1.center, sphere2.center);\r\n    float sumradius = sphere1.radius + sphere2.radius;\r\n\r\n    if (d < sumradius)\r\n    {\r\n        res.penetrationDepth = sumradius - d;\r\n        res.normal = (sphere1.center - sphere2.center).normalized;\r\n        res.point = sphere2.center + res.normal * sphere2.radius;\r\n        res.fact = true;\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Sphere sphere1 = Sphere(Vector3f(0, 0, 0), 1.0f);\r\n    Sphere sphere2 = Sphere(Vector3f(1.9f, 0, 0), 1.0f);\r\n    Intersection isec = intrSphereVsSphere(sphere1, sphere2);\r\n    assert(isec.fact);\r\n    assert(isConsiderZero(isec.penetrationDepth - 0.1f));\r\n    assert(isAlmostZero(isec.point - Vector3f(0.9f, 0.0f, 0.0f)));\r\n    assert(isAlmostZero(isec.normal - Vector3f(-1.0f, 0.0f, 0.0f)));\r\n}\r\n\r\n/// Checks sphere and plane for intersection\r\nIntersection intrSphereVsPlane(ref Sphere sphere, ref Plane plane)\r\n{\r\n    Intersection res;\r\n    res.fact = false;\r\n\r\n    float q = plane.normal.dot(sphere.center - plane.d).abs;\r\n\r\n    if (q <= sphere.radius)\r\n    {\r\n        res.penetrationDepth = sphere.radius - q;\r\n        res.normal = plane.normal;\r\n        res.point = sphere.center - res.normal * sphere.radius;\r\n        res.fact = true;\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Sphere sphere = Sphere(Vector3f(0, 0.9f, 0), 1.0f);\r\n    Plane plane = Plane(Vector3f(0, 1, 0), 0.0f);\r\n    Intersection isec = intrSphereVsPlane(sphere, plane);\r\n    assert(isec.fact);\r\n    assert(isConsiderZero(isec.penetrationDepth - 0.1f));\r\n    assert(isAlmostZero(isec.point - Vector3f(0.0f, -0.1f, 0.0f)));\r\n    assert(isAlmostZero(isec.normal - Vector3f(0.0f, 1.0f, 0.0f)));\r\n}\r\n\r\nprivate void measureSphereAndTriVert(\r\n        Vector3f center,\r\n        float radius,\r\n        ref Intersection result,\r\n        Triangle tri,\r\n        int whichVert)\r\n{\r\n    Vector3f diff = center - tri.v[whichVert];\r\n    float len = diff.length;\r\n    float penetrate = radius - len;\r\n    if (penetrate > 0.0f)\r\n    {\r\n        result.fact = true;\r\n        result.penetrationDepth = penetrate;\r\n        result.normal = diff * (1.0f / len);\r\n        result.point = center - result.normal * radius;\r\n    }\r\n}\r\n\r\nvoid measureSphereAndTriEdge(\r\n        Vector3f center,\r\n        float radius,\r\n        ref Intersection result,\r\n        Triangle tri,\r\n        int whichEdge)\r\n{\r\n    static int[] nextDim1 = [1, 2, 0];\r\n    static int[] nextDim2 = [2, 0, 1];\r\n\r\n    int whichVert0, whichVert1;\r\n    whichVert0 = whichEdge;\r\n    whichVert1 = nextDim1[whichEdge];\r\n    float penetrate;\r\n    Vector3f dir = tri.edges[whichEdge];\r\n    float edgeLen = dir.length;\r\n    if (isConsiderZero(edgeLen))\r\n        dir = Vector3f(0.0f, 0.0f, 0.0f);\r\n    else\r\n        dir *= (1.0f / edgeLen);\r\n    Vector3f vert2Point = center - tri.v[whichVert0];\r\n    float dot = dir.dot(vert2Point);\r\n    Vector3f project = tri.v[whichVert0] + dir * dot;\r\n    if (dot > 0.0f && dot < edgeLen)\r\n    {\r\n        Vector3f diff = center - project;\r\n        float len = diff.length;\r\n        penetrate = radius - len;\r\n        if (penetrate > 0.0f && penetrate < result.penetrationDepth && penetrate < radius)\r\n        {\r\n            result.fact = true;\r\n            result.penetrationDepth = penetrate;\r\n            result.normal = diff * (1.0f / len);\r\n            result.point = center - result.normal * radius;\r\n        }\r\n    }\r\n}\r\n\r\n/// Checks sphere and triangle for intersection\r\nIntersection intrSphereVsTriangle(ref Sphere sphere, ref Triangle tri)\r\n{\r\n    Intersection result;\r\n    result.point = Vector3f(0.0f, 0.0f, 0.0f);\r\n    result.normal = Vector3f(0.0f, 0.0f, 0.0f);\r\n    result.penetrationDepth = 1.0e5f;\r\n    result.fact = false;\r\n\r\n    float distFromPlane = tri.normal.dot(sphere.center) - tri.d;\r\n\r\n    float factor = 1.0f;\r\n\r\n    if (distFromPlane < 0.0f)\r\n        factor = -1.0f;\r\n\r\n    float penetrated = sphere.radius - distFromPlane * factor;\r\n\r\n    if (penetrated <= 0.0f)\r\n        return result;\r\n\r\n    Vector3f contactB = sphere.center - tri.normal * distFromPlane;\r\n\r\n    int pointInside = tri.isPointInside(contactB);\r\n\r\n    if (pointInside == -1) // inside the triangle\r\n    {\r\n        result.penetrationDepth = penetrated;\r\n        result.point = sphere.center - tri.normal * factor * sphere.radius; //on the sphere\r\n        result.fact = true;\r\n        result.normal = tri.normal * factor;\r\n        return result;\r\n    }\r\n\r\n    switch (pointInside)\r\n    {\r\n        case 0:\r\n            measureSphereAndTriVert(sphere.center, sphere.radius, result, tri, 0);\r\n            break;\r\n        case 1:\r\n            measureSphereAndTriEdge(sphere.center, sphere.radius, result, tri, 0);\r\n            break;\r\n        case 2:\r\n            measureSphereAndTriVert(sphere.center, sphere.radius, result, tri, 1);\r\n            break;\r\n        case 3:\r\n            measureSphereAndTriEdge(sphere.center, sphere.radius, result, tri, 1);\r\n            break;\r\n        case 4:\r\n            measureSphereAndTriVert(sphere.center, sphere.radius, result, tri, 2);\r\n            break;\r\n        case 5:\r\n            measureSphereAndTriEdge(sphere.center, sphere.radius, result, tri, 2);\r\n            break;\r\n        default:\r\n            break;\r\n    }\r\n\r\n    return result;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Sphere sphere = Sphere(Vector3f(0, 0.9f, 0), 1.0f);\r\n    \r\n    Triangle tri;\r\n    tri.v = [\r\n        Vector3f(0.5f, 0, -0.5f),\r\n        Vector3f(-0.5f, 0, -0.5f),\r\n        Vector3f(0, 0, 0.5f)\r\n    ];\r\n    tri.normal = Vector3f(0, 1, 0);\r\n    tri.d = 0.0f;\r\n    \r\n    Intersection isec = intrSphereVsTriangle(sphere, tri);\r\n    assert(isec.fact);\r\n    assert(isConsiderZero(isec.penetrationDepth - 0.1f));\r\n    assert(isAlmostZero(isec.point - Vector3f(0.0f, -0.1f, 0.0f)));\r\n    assert(isAlmostZero(isec.normal - Vector3f(0.0f, 1.0f, 0.0f)));\r\n}\r\n\r\n/// Checks sphere and AABB for intersection\r\nIntersection intrSphereVsAABB(ref Sphere sphere, ref AABB aabb)\r\n{\r\n    Intersection result;\r\n    result.penetrationDepth = 0.0f;\r\n    result.normal = Vector3f(0.0f, 0.0f, 0.0f);\r\n    result.fact = false;\r\n\r\n    if (aabb.containsPoint(sphere.center))\r\n    {\r\n        result.penetrationDepth = distance(aabb.center, sphere.center);\r\n        result.normal = (aabb.center - sphere.center) / result.penetrationDepth;\r\n        result.point = sphere.center + result.normal * sphere.radius;\r\n        result.fact = true;\r\n        return result;\r\n    }\r\n    else\r\n    {\r\n        Vector3f closest = aabb.closestPoint(sphere.center);\r\n        Vector3f delta = closest - sphere.center;\r\n\r\n        float distSquared = delta.lengthsqr();\r\n        if (distSquared > sphere.radius * sphere.radius)\r\n            return result;\r\n\r\n        result.fact = true;\r\n        float dist = sqrt(distSquared);\r\n        result.penetrationDepth = sphere.radius - dist;\r\n        result.normal = delta / dist;\r\n        result.point = sphere.center + result.normal * sphere.radius;\r\n        return result;\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Sphere sphere = Sphere(Vector3f(1.5f, 0.0f, 0.0f), 1.0f);\r\n    AABB aabb = AABB(Vector3f(0, 0, 0), Vector3f(1, 1, 1));\r\n    Intersection intr = intrSphereVsAABB(sphere, aabb);\r\n    assert(intr.fact);\r\n    assert(isAlmostZero(intr.normal - Vector3f(-1.0f, 0.0f, 0.0f)));\r\n    assert(isConsiderZero(intr.penetrationDepth - 0.5f));\r\n}\r\n\r\n/// Checks sphere and OBB for intersection\r\nIntersection intrSphereVsOBB(ref Sphere s, ref OBB b)\r\n{\r\n    Intersection intr;\r\n    intr.fact = false;\r\n    intr.penetrationDepth = 0.0;\r\n    intr.normal = Vector3f(0.0f, 0.0f, 0.0f);\r\n    intr.point = Vector3f(0.0f, 0.0f, 0.0f);\r\n\r\n    Vector3f relativeCenter = s.center - b.transform.translation;\r\n    relativeCenter = b.transform.invRotate(relativeCenter);\r\n\r\n    if (abs(relativeCenter.x) - s.radius > b.extent.x ||\r\n        abs(relativeCenter.y) - s.radius > b.extent.y ||\r\n        abs(relativeCenter.z) - s.radius > b.extent.z)\r\n        return intr;\r\n\r\n    Vector3f closestPt = Vector3f(0.0f, 0.0f, 0.0f);\r\n    float distance;\r\n\r\n    distance = relativeCenter.x;\r\n    if (distance >  b.extent.x) distance =  b.extent.x;\r\n    if (distance < -b.extent.x) distance = -b.extent.x;\r\n    closestPt.x = distance;\r\n\r\n    distance = relativeCenter.y;\r\n    if (distance >  b.extent.y) distance =  b.extent.y;\r\n    if (distance < -b.extent.y) distance = -b.extent.y;\r\n    closestPt.y = distance;\r\n\r\n    distance = relativeCenter.z;\r\n    if (distance >  b.extent.z) distance =  b.extent.z;\r\n    if (distance < -b.extent.z) distance = -b.extent.z;\r\n    closestPt.z = distance;\r\n\r\n    float distanceSqr = (closestPt - relativeCenter).lengthsqr;\r\n    if (distanceSqr > s.radius * s.radius)\r\n    return intr;\r\n\r\n    Vector3f closestPointWorld = closestPt * b.transform;\r\n\r\n    intr.fact = true;\r\n    intr.normal = -(closestPointWorld - s.center).normalized;\r\n    intr.point = closestPointWorld;\r\n    intr.penetrationDepth = s.radius - sqrt(distanceSqr);\r\n\r\n    return intr;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Sphere sphere = Sphere(Vector3f(0, 1.9f, 0), 1.0f);\r\n    OBB obb = OBB(Vector3f(0, 0, 0), Vector3f(1, 1, 1));\r\n    \r\n    Intersection isec = intrSphereVsOBB(sphere, obb);\r\n    assert(isec.fact);\r\n    assert(isConsiderZero(isec.penetrationDepth - 0.1f));\r\n    assert(isAlmostZero(isec.point - Vector3f(0.0f, 1.0f, 0.0f)));\r\n    assert(isAlmostZero(isec.normal - Vector3f(0.0f, 1.0f, 0.0f)));\r\n}\r\n"
  },
  {
    "path": "dlib/geometry/mpr.d",
    "content": "/*\nCopyright (c) 2013-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Copyright: Timur Gafarov 2013-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.geometry.mpr;\n\nimport dlib.math.vector;\nimport dlib.math.matrix;\nimport dlib.math.transformation;\nimport dlib.math.utils;\nimport dlib.geometry.intersection;\nimport dlib.geometry.support;\n\n/**\n * Implementation of the Minkowski Portal Refinement algorithm.\n * Detects intersection between two arbitrary convex shapes\n * defined by their support functions and transformation matrices.\n */\nIntersection intrMPR(S1, S2)(\n    S1 s1, Matrix4x4f transform1,\n    S2 s2, Matrix4x4f transform2)\n{\n    enum float collideEpsilon = 1e-4f;\n    enum maxIterations = 10;\n\n    // Used variables\n    Vector3f temp1;\n    Vector3f v01, v02, v0;\n    Vector3f v11, v12, v1;\n    Vector3f v21, v22, v2;\n    Vector3f v31, v32, v3;\n    Vector3f v41, v42, v4;\n\n    // Initialization of the output\n    Intersection c;\n    c.point = Vector3f(0.0f, 0.0f, 0.0f);\n    c.normal = Vector3f(0.0f, 0.0f, 0.0f);\n    c.penetrationDepth = 0.0f;\n    c.fact = false;\n\n    // Get the center of shape1 in world coordinates\n    v01 = transform1.translation;\n\n    // Get the center of shape2 in world coordinates\n    v02 = transform2.translation;\n\n    // v0 is the center of the Minkowski difference\n    v0 = v02 - v01;\n\n    // Avoid case where centers overlap - any direction is fine in this case\n    if (v0.isAlmostZero)\n        v0 = Vector3f(0.00001f, 0.0f, 0.0f);\n\n    // v1 = support in direction of origin\n    c.normal = -v0;\n\n    v11 = supTransformed(s1, transform1, v0);\n    v12 = supTransformed(s2, transform2, c.normal);\n    v1 = v12 - v11;\n\n    if (dot(v1, c.normal) <= 0.0f)\n    {\n        c.fact = false;\n        return c;\n    }\n    \n    // v2 = support perpendicular to v1,v0\n    c.normal = cross(v1, v0);\n\n    if (c.normal.isAlmostZero)\n    {\n        c.normal = v1 - v0;\n        c.normal.normalize();\n\n        c.point = v11;\n        c.point += v12;\n        c.point *= 0.5f;\n\n        c.penetrationDepth = dot(v12 - v11, c.normal);\n\n        c.fact = true;\n        return c;\n    }\n\n    v21 = supTransformed(s1, transform1, -c.normal);\n    v22 = supTransformed(s2, transform2,  c.normal);\n    v2 = v22 - v21;\n\n    if (dot(v2, c.normal) <= 0.0f)\n    {\n        c.fact = false;\n        return c;\n    }\n    \n    // Determine whether origin is on + or - side of plane (v1,v0,v2)\n    c.normal = cross(v1 - v0, v2 - v0);\n\n    float dist = dot(c.normal, v0);\n\n    // If the origin is on the - side of the plane, reverse the direction of the plane\n    if (dist > 0.0f)\n    {\n        swap(&v1, &v2);\n        swap(&v11, &v21);\n        swap(&v12, &v22);\n        c.normal = -c.normal;\n    }\n\n    int phase2 = 0;\n    int phase1 = 0;\n    bool hit = false;\n\n    // Phase One: Identify a portal\n    while (true)\n    {\n        if (phase1 > maxIterations)\n        {\n            c.fact = false;\n            return c;\n        }\n\n        phase1++;\n\n        // Obtain the support point in a direction perpendicular to the existing plane\n        // Note: This point is guaranteed to lie off the plane\n        v31 = supTransformed(s1, transform1, -c.normal);\n        v32 = supTransformed(s2, transform2,  c.normal);\n        v3 = v32 - v31;\n\n        if (dot(v3, c.normal) <= 0.0f)\n        {\n            c.fact = false;\n            return c;\n        }\n        \n        // If origin is outside (v1,v0,v3), then eliminate v2 and loop\n        temp1 = cross(v1, v3);\n        if (dot(temp1, v0) < 0.0f)\n        {\n            v2 = v3;\n            v21 = v31;\n            v22 = v32;\n            c.normal = cross(v1 - v0, v3 - v0);\n            continue;\n        }\n\n        // If origin is outside (v3,v0,v2), then eliminate v1 and loop\n        temp1 = cross(v3, v2);\n        if (dot(temp1, v0) < 0.0f)\n        {\n            v1 = v3;\n            v11 = v31;\n            v12 = v32;\n            c.normal = cross(v3 - v0, v2 - v0);\n            continue;\n        }\n\n        // Phase Two: Refine the portal\n        // We are now inside of a wedge...\n        while (true)\n        {\n            phase2++;\n\n            // Compute normal of the wedge face\n            c.normal = cross(v2 - v1, v3 - v1);\n\n            // Can this happen? Can it be handled more cleanly?\n            //if (c.normal.isAlmostZero)\n            //    return true;\n\n            c.normal.normalize();\n\n            // Compute distance from origin to wedge face\n            float d = dot(c.normal, v1);\n\n            // If the origin is inside the wedge, we have a hit\n            if (d >= 0 && !hit)\n                hit = true;\n\n            // Find the support point in the direction of the wedge face\n            v41 = supTransformed(s1, transform1, -c.normal);\n            v42 = supTransformed(s2, transform1, c.normal);\n            v4 = v42 - v41;\n\n            float delta = dot(v4 - v3, c.normal);\n            c.penetrationDepth = dot(v4, c.normal);\n\n            // If the boundary is thin enough or the origin is outside\n            // the support plane for the newly discovered vertex, then we can terminate\n            if (delta <= collideEpsilon || c.penetrationDepth <= 0.0f || phase2 > maxIterations)\n            {\n                if (hit)\n                {\n                    float b0 = dot(cross(v1, v2), v3);\n                    float b1 = dot(cross(v3, v2), v0);\n                    float b2 = dot(cross(v0, v1), v3);\n                    float b3 = dot(cross(v2, v1), v0);\n\n                    float sum = b0 + b1 + b2 + b3;\n\n                    if (sum <= 0)\n                    {\n                        b0 = 0;\n                        b1 = dot(cross(v2, v3), c.normal);\n                        b2 = dot(cross(v3, v1), c.normal);\n                        b3 = dot(cross(v1, v2), c.normal);\n\n                        sum = b1 + b2 + b3;\n                    }\n\n                    float inv = 1.0f / sum;\n\n                    c.point = v01 * b0;\n                    c.point += v11 * b1;\n                    c.point += v21 * b2;\n                    c.point += v31 * b3;\n\n                    c.point += v02 * b0;\n                    c.point += v12 * b1;\n                    c.point += v22 * b2;\n                    c.point += v32 * b3;\n\n                    c.point *= inv * 0.5f;\n                }\n\n                c.fact = hit;\n                return c;\n            }\n\n            // Compute the tetrahedron dividing face (v4,v0,v3)\n            temp1 = cross(v4, v0);\n            float d2 = dot(temp1, v1);\n\n            if (d2 >= 0.0f)\n            {\n                d2 = dot(temp1, v2);\n                if (d2 >= 0.0f)\n                {\n                    // Inside d1 & inside d2 ==> eliminate v1\n                    v1 = v4;\n                    v11 = v41;\n                    v12 = v42;\n                }\n                else\n                {\n                    // Inside d1 & outside d2 ==> eliminate v3\n                    v3 = v4;\n                    v31 = v41;\n                    v32 = v42;\n                }\n            }\n            else\n            {\n                d2 = dot(temp1, v3);\n\n                if (d2 >= 0.0f)\n                {\n                    // Outside d1 & inside d3 ==> eliminate v2\n                    v2 = v4;\n                    v21 = v41;\n                    v22 = v42;\n                }\n                else\n                {\n                    // Outside d1 & outside d3 ==> eliminate v1\n                    v1 = v4;\n                    v11 = v41;\n                    v12 = v42;\n                }\n            }\n        }\n    }\n\n    // Should never get here\n    assert(0);\n}\n"
  },
  {
    "path": "dlib/geometry/obb.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.geometry.obb;\r\n\r\nimport dlib.math.vector;\r\nimport dlib.math.matrix;\r\nimport dlib.math.transformation;\r\n\r\n/// Oriented bounding box\r\nstruct OBB\r\n{\r\n    Vector3f extent;\r\n    Matrix4x4f transform;\r\n\r\n    this(Vector3f position, Vector3f size)\r\n    {\r\n        transform = Matrix4x4f.identity;\r\n        center = position;\r\n        extent = size;\r\n    }\r\n\r\n    @property\r\n    {\r\n        Vector3f center()\r\n        {\r\n            return transform.translation;\r\n        }\r\n\r\n        Vector3f center(Vector3f v)\r\n        {\r\n            transform.a14 = v.x;\r\n            transform.a24 = v.y;\r\n            transform.a34 = v.z;\r\n            return v;\r\n        }\r\n    }\r\n\r\n    @property Matrix3x3f orient()\r\n    {\r\n        return matrix4x4to3x3(transform);\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/geometry/package.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Computational geometry\r\n *\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.geometry;\r\n\r\npublic\r\n{\r\n    import dlib.geometry.aabb;\r\n    import dlib.geometry.frustum;\r\n    import dlib.geometry.intersection;\r\n    import dlib.geometry.mpr;\r\n    import dlib.geometry.obb;\r\n    import dlib.geometry.plane;\r\n    import dlib.geometry.ray;\r\n    import dlib.geometry.sphere;\r\n    import dlib.geometry.support;\r\n    import dlib.geometry.triangle;\r\n    import dlib.geometry.trimesh;\r\n    import dlib.geometry.utils;\r\n}\r\n"
  },
  {
    "path": "dlib/geometry/plane.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.geometry.plane;\r\n\r\nimport std.math;\r\nimport dlib.math.vector;\r\nimport dlib.math.utils;\r\n\r\n/// Infinite plane\r\nstruct Plane\r\n{\r\n    /// Return a Plane with all values at zero\r\n    static Plane opCall()\r\n    {\r\n        return Plane(0.0f, 0.0f, 0.0f, 0.0f);\r\n    }\r\n\r\n    /// Return a Plane with the Vec3f component of n and distance of d\r\n    static Plane opCall(Vector3f n, float d)\r\n    {\r\n        return Plane(n.x, n.y, n.z, d);\r\n    }\r\n\r\n    /// Return a Plane with a Vec3f component of x, y, z and distance of d\r\n    static Plane opCall(float x, float y, float z, float d)\r\n    {\r\n        Plane p;\r\n        p.x = x;\r\n        p.y = y;\r\n        p.z = z;\r\n        p.d = d;\r\n        return p;\r\n    }\r\n\r\n    void fromPoints(Vector3f p0, Vector3f p1, Vector3f p2)\r\n    {\r\n        Vector3f v0 = p0 - p1;\r\n        Vector3f v1 = p2 - p1;\r\n        Vector3f n = cross(v1, v0);\r\n        n.normalize();\r\n        x = n.x;\r\n        y = n.y;\r\n        z = n.z;\r\n        d = -(p0.x * x + p0.y * y + p0.z * z);\r\n    }\r\n\r\n    void fromPointAndNormal(Vector3f p, Vector3f n)\r\n    {\r\n        n.normalize();\r\n        x = n.x;\r\n        y = n.y;\r\n        z = n.z;\r\n        d = -(p.x * x + p.y * y + p.z * z);\r\n    }\r\n\r\n    float dot(Vector3f p)\r\n    {\r\n        return x * p.x + y * p.y + z * p.z;\r\n    }\r\n\r\n    void normalize()\r\n    {\r\n        float len = sqrt(x * x + y * y + z * z);\r\n        x /= len;\r\n        y /= len;\r\n        z /= len;\r\n        d /= len;\r\n    }\r\n\r\n    Plane normalized()\r\n    {\r\n        Plane res;\r\n        float len = sqrt(x * x + y * y + z * z);\r\n        return Plane(x / len, y / len, z / len, d / len);\r\n    }\r\n\r\n   /**\r\n    * Get the distance from the center of the plane to the given point.\r\n    * This is useful for determining which side of the plane the point is on.\r\n    */\r\n    float distance(Vector3f p)\r\n    {\r\n        return x * p.x + y * p.y + z * p.z + d;\r\n    }\r\n\r\n    Vector3f reflect(Vector3f vec)\r\n    {\r\n        float d = distance(vec);\r\n        return vec + Vector3f(-x, -y, -z) * 2 * d;\r\n    }\r\n\r\n    Vector3f project(Vector3f p)\r\n    {\r\n        float h = distance(p);\r\n        return Vector3f(p.x - x * h,\r\n                        p.y - y * h,\r\n                        p.z - z * h);\r\n    }\r\n\r\n    bool isOnPlane(Vector3f p, float threshold = 0.001f)\r\n    {\r\n        float d = distance(p);\r\n        if (d < threshold && d > -threshold)\r\n            return true;\r\n        return false;\r\n    }\r\n\r\n   /**\r\n    * Calculate the intersection between this plane and a line\r\n    * If the plane and the line are parallel, false is returned\r\n    */\r\n    bool intersectsLine(Vector3f p0, Vector3f p1, ref float t)\r\n    {\r\n        Vector3f dir = p1 - p0;\r\n        float div = dot(dir);\r\n        if (div == 0.0)\r\n            return false;\r\n        t = -distance(p0) / div;\r\n        return true;\r\n    }\r\n\r\n    bool intersectsLine(Vector3f p0, Vector3f p1, ref Vector3f ip)\r\n    {\r\n        Vector3f dir = p1 - p0;\r\n        float div = dot(dir);\r\n        if (div == 0.0)\r\n        {\r\n            ip = (p0 + p1) * 0.5f;\r\n            return false;\r\n        }\r\n        float u = -distance(p0) / div;\r\n        ip = p0 + (p1 - p0) * u;\r\n        return true;\r\n    }\r\n\r\n    bool intersectsLineSegment(Vector3f p0, Vector3f p1, ref Vector3f ip)\r\n    {\r\n        Vector3f ray = p1 - p0;\r\n\r\n        // calculate plane\r\n        float d = dot(position);\r\n        float dr = dot(ray);\r\n\r\n        if (abs(dr) < EPSILON)\r\n            return false; // avoid divide by zero\r\n\r\n        // Compute the t value for the directed line ray intersecting the plane\r\n        float t = (d - dot(p0)) / dr;\r\n\r\n        // scale the ray by t\r\n        Vector3f newRay = ray * t;\r\n\r\n        // calc contact point\r\n        ip = p0 + newRay;\r\n\r\n        if (t >= 0.0 && t <= 1.0)\r\n            return true; // line intersects plane\r\n\r\n        return false; // line does not\r\n    }\r\n\r\n    float opIndex(size_t i)\r\n    {\r\n        return arrayof[i];\r\n    }\r\n\r\n    float opIndexAssign(float value, size_t i)\r\n    {\r\n        return (arrayof[i] = value);\r\n    }\r\n\r\n    union\r\n    {\r\n        float[4] arrayof;\r\n        Vector4f vectorof;\r\n\r\n        struct\r\n        {\r\n            float a, b, c, d;\r\n        }\r\n\r\n        Vector3f normal;\r\n    }\r\n\r\n    alias vectorof this;\r\n\r\n    @property Vector3f position()\r\n    {\r\n        return -(normal * d);\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Plane plane = Plane(Vector3f(0, 1, 0), 0.0f);\r\n    assert(isConsiderZero(plane.distance(Vector3f(0, -1, 0)) + 1.0f));\r\n    float d = plane.dot(Vector3f(1, 0, 0));\r\n    assert(isConsiderZero(d));\r\n    assert(isAlmostZero(plane.position));\r\n    assert(plane.isOnPlane(Vector3f(0, 0, 0)));\r\n    float t;\r\n    assert(plane.intersectsLine(Vector3f(0, -1, 0), Vector3f(0, 1, 0), t));\r\n    assert(isConsiderZero(t - 0.5f));\r\n    Vector3f ip;\r\n    assert(plane.intersectsLineSegment(Vector3f(0, -1, 0), Vector3f(0, 1, 0), ip));\r\n}\r\n"
  },
  {
    "path": "dlib/geometry/ray.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.geometry.ray;\r\n\r\nimport std.math;\r\nimport dlib.math.vector;\r\nimport dlib.math.utils;\r\nimport dlib.geometry.sphere;\r\nimport dlib.geometry.triangle;\r\n\r\n/// Ray with starting and ending points\r\nstruct Ray\r\n{\r\n    Vector3f p0;\r\n    Vector3f p1;\r\n    float t;\r\n\r\n    this(Vector3f begin, Vector3f end)\r\n    {\r\n        p0 = begin;\r\n        p1 = end;\r\n    }\r\n    \r\n    bool intersectSphere(Sphere sphere, out Vector3f intersectionPoint)\r\n    {\r\n        Vector3f dir = p1 - p0;\r\n        dir.normalize();\r\n        Vector3f dist = sphere.center - p0;\r\n        float B = dot(dist,dir);\r\n        float D = sphere.radius * sphere.radius - dot(dist,dist) + B * B;\r\n        if (D < 0.0f)\r\n        {\r\n            intersectionPoint = Vector3f(0.0f, 0.0f, 0.0f);\r\n            return false;\r\n        }\r\n        float t0 = B - sqrt(D);\r\n        float t1 = B + sqrt(D);\r\n        if (t0 > 0.0f)\r\n        {\r\n            t = t0;\r\n            intersectionPoint = p0 + dir * t0;\r\n            return true;\r\n        }\r\n        if (t1 > 0.0f)\r\n        {\r\n            t = t1;\r\n            intersectionPoint = p0 + dir * t1;\r\n            return true;\r\n        }\r\n        intersectionPoint = Vector3f(0.0f, 0.0f, 0.0f);\r\n        return false;\r\n    }\r\n    \r\n    bool intersectTriangle(Triangle tri, out Vector3f intersectionPoint)\r\n    {\r\n        Vector3f u, v, n;    // triangle vectors\r\n        Vector3f dir, w0, w; // ray vectors\r\n        float r, a, b;       // params to calc ray-plane intersect\r\n\r\n        // get triangle edge vectors and plane normal\r\n        u = tri.v[1] - tri.v[0];\r\n        v = tri.v[2] - tri.v[0];\r\n        n = cross(u, v); // cross product\r\n        if (n.isZero)    // triangle is degenerate\r\n        {\r\n            intersectionPoint = Vector3f(0.0f, 0.0f, 0.0f);\r\n            return false;\r\n        }\r\n\r\n        dir = p1 - p0; // ray direction vector\r\n        w0 = p0 - tri.v[0];\r\n        a = -dot(n, w0);\r\n        b = dot(n, dir);\r\n\r\n        if (fabs(b) < EPSILON) // ray is parallel to triangle plane\r\n        {\r\n            // no intersect\r\n            intersectionPoint = Vector3f(0.0f, 0.0f, 0.0f);\r\n            return false;\r\n        }\r\n\r\n        // get intersect point of ray with triangle plane\r\n        r = a / b;\r\n        if (r < 0.0f) // ray goes away from triangle\r\n        {\r\n            // no intersect\r\n            intersectionPoint = Vector3f(0.0f, 0.0f, 0.0f);\r\n            return false;\r\n        }\r\n\r\n        Vector3f I = p0 + dir * r; // intersect point of ray and plane\r\n\r\n        float uu, uv, vv, wu, wv, D;\r\n        uu = dot(u, u);\r\n        uv = dot(u, v);\r\n        vv = dot(v, v);\r\n        w = I - tri.v[0];\r\n        wu = dot(w, u);\r\n        wv = dot(w, v);\r\n        D = uv * uv - uu * vv;\r\n\r\n        // get and test parametric coords\r\n        float s, t;\r\n        s = (uv * wv - vv * wu) / D;\r\n        if (s < 0.0 || s > 1.0)        // point is outside of the triangle\r\n        {\r\n            intersectionPoint = Vector3f(0.0f, 0.0f, 0.0f);\r\n            return false;\r\n        }\r\n        t = (uv * wu - uu * wv) / D;\r\n        if (t < 0.0 || (s + t) > 1.0)  // point is outside of the triangle\r\n        {\r\n            intersectionPoint = Vector3f(0.0f, 0.0f, 0.0f);\r\n            return false;\r\n        }\r\n\r\n        intersectionPoint = I; // point is inside of the triangle\r\n        return true;\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Ray r = Ray(Vector3f(0, 0, 0), Vector3f(10, 0, 0));\r\n    Sphere s = Sphere(Vector3f(3, 0, 0), 1);\r\n    Vector3f intersectionPoint;\r\n    assert(r.intersectSphere(s, intersectionPoint));\r\n    assert(isAlmostZero(intersectionPoint - Vector3f(2, 0, 0)));\r\n    \r\n    Triangle tri;\r\n    tri.v[0] = Vector3f(5, -1, 1);\r\n    tri.v[1] = Vector3f(5, -1, -1);\n    tri.v[2] = Vector3f(5, 1, 0);\r\n    assert(r.intersectTriangle(tri, intersectionPoint));\r\n    assert(isAlmostZero(intersectionPoint - Vector3f(5, 0, 0)));\r\n}\r\n"
  },
  {
    "path": "dlib/geometry/sphere.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.geometry.sphere;\r\n\r\nimport dlib.math.vector;\r\n\r\n/// Sphere object\r\nstruct Sphere\r\n{\r\n    Vector3f center;\r\n    float radius;\r\n\r\n    this(Vector3f newCenter, float newRadius)\r\n    {\r\n        center = newCenter;\r\n        radius = newRadius;\r\n    }\r\n\r\n    bool containsPoint(Vector3f pt)\r\n    {\r\n        float dist = (pt - center).lengthsqr;\r\n        return dist < (radius * radius) ? true : false;\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Sphere sphere = Sphere(Vector3f(0, 0, 0), 1.0f);\r\n    assert(sphere.containsPoint(Vector3f(0.1f, 0, 0)));\r\n}\r\n"
  },
  {
    "path": "dlib/geometry/support.d",
    "content": "/*\nCopyright (c) 2011-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Copyright: Timur Gafarov 2011-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.geometry.support;\n\nimport std.math;\nimport dlib.math.vector;\nimport dlib.math.matrix;\nimport dlib.math.utils;\n\n/// Sphere support function\nVector3f supSphere(Vector3f dir, float radius)\n{\n    return dir * radius;\n}\n\n/// Ellipsoid support function\nVector3f supEllipsoid(Vector3f dir, Vector3f radii)\n{\n    return dir * radii;\n}\n\n/// AABB support function\nVector3f subBox(Vector3f dir, Vector3f halfSize)\n{\n    Vector3f result;\n    result.x = sign(dir.x) * halfSize.x;\n    result.y = sign(dir.y) * halfSize.y;\n    result.z = sign(dir.z) * halfSize.z;\n    return result;\n}\n\n/// Cylinder support function\nVector3f supCylinder(Vector3f dir, float radius, float height)\n{\n    Vector3f result;\n    float sigma = sqrt((dir.x * dir.x + dir.z * dir.z));\n\n    if (sigma > 0.0f)\n    {\n        result.x = dir.x / sigma * radius;\n        result.y = sign(dir.y) * height * 0.5f;\n        result.z = dir.z / sigma * radius;\n    }\n    else\n    {\n        result.x = 0.0f;\n        result.y = sign(dir.y) * height * 0.5f;\n        result.z = 0.0f;\n    }\n\n    return result;\n}\n\n/// Cone support function\nVector3f supCone(Vector3f dir, float radius, float height)\n{\n    float zdist = dir[0] * dir[0] + dir[1] * dir[1];\n    float len = zdist + dir[2] * dir[2];\n    zdist = sqrt(zdist);\n    len = sqrt(len);\n    float half_h = height * 0.5;\n\n    float sin_a = radius / sqrt(radius * radius + 4.0f * half_h * half_h);\n\n    if (dir[2] > len * sin_a)\n        return Vector3f(0.0f, 0.0f, half_h);\n    else if (zdist > 0.0f)\n    {\n        float rad = radius / zdist;\n        return Vector3f(rad * dir[0], rad * dir[1], -half_h);\n    }\n    else\n        return Vector3f(0.0f, 0.0f, -half_h);\n}\n\n/// Capsule support function\nVector3f supCapsule(Vector3f dir, float radius, float height)\n{\n    float half_h = height * 0.5f;\n    Vector3f pos1 = Vector3f(0.0f, 0.0f, half_h);\n    Vector3f pos2 = Vector3f(0.0f, 0.0f, -half_h);\n    Vector3f v = dir * radius;\n    pos1 += v;\n    pos2 += v;\n    if (dir.dot(pos1) > dir.dot(pos2))\n        return pos1;\n    else return pos2;\n}\n\n/// Convex hull support function\nVector3f supConvexHull(Vector3f dir, Vector3f[] vertices)\n{\n    float maxdot = -float.max;\n    Vector3f bestv;\n    foreach(v; vertices)\n    {\n        float d = dir.dot(v);\n        if (d > maxdot)\n        {\n            bestv = v;\n            maxdot = d;\n        }\n    }\n    return bestv;\n}\n\n/// Triangle support function\nVector3f supTriangle(Vector3f dir, Vector3f[3] v)\n{\n    float dota = dir.dot(v[0]);\n    float dotb = dir.dot(v[1]);\n    float dotc = dir.dot(v[2]);\n\n    if (dota > dotb)\n    {\n        if (dotc > dota)\n            return v[2];\n        else\n            return v[0];\n    }\n    else\n    {\n        if (dotc > dotb)\n            return v[2];\n        else\n            return v[1];\n    }\n}\n\n/// Transformed support function\nVector3f supTransformed(S)(S shape, Matrix4x4f transformation, Vector3f dir)\n{\n    Vector3f result;\n    result.x = ((dir.x * m.a11) + (dir.y * m.a21)) + (dir.z * m.a31);\n    result.y = ((dir.x * m.a12) + (dir.y * m.a22)) + (dir.z * m.a32);\n    result.z = ((dir.x * m.a13) + (dir.y * m.a23)) + (dir.z * m.a33);\n\n    result = shape.support(result);\n\n    float x = ((result.x * m.a11) + (result.y * m.a12)) + (result.z * m.a13);\n    float y = ((result.x * m.a21) + (result.y * m.a22)) + (result.z * m.a23);\n    float z = ((result.x * m.a31) + (result.y * m.a32)) + (result.z * m.a33);\n\n    result.x = m.a14 + x;\n    result.y = m.a24 + y;\n    result.z = m.a34 + z;\n    \n    return Vector3f;\n}\n"
  },
  {
    "path": "dlib/geometry/triangle.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.geometry.triangle;\r\n\r\nprivate\r\n{\r\n    import std.math;\r\n    import dlib.math.vector;\r\n    import dlib.geometry.aabb;\r\n}\r\n\r\n/// Triangle object\r\nstruct Triangle\r\n{\r\n    Vector3f[3] v;\r\n    Vector3f[3] n;\r\n    Vector2f[3] t1;\r\n    Vector2f[3] t2;\r\n    Vector4f[3] tg;\r\n\r\n    Vector3f[3] edges;\r\n    Vector3f normal;\r\n    Vector3f barycenter;\r\n    float d;\r\n\r\n    int materialIndex;\r\n\r\n    /// Returns -1 if given point is inside the triangle\r\n    int isPointInside(Vector3f point)\r\n    {\r\n        //select coordinate\r\n        int dim0, dim1, plane;\r\n        float clockness; // 1.0 counter clockwise, -1.0 clockwise\r\n\r\n        if (abs(normal[1]) > abs(normal[2]))\r\n        {\r\n            if (abs(normal[1]) > abs(normal[0])) //use y plane\r\n            {\r\n                plane = 1;\r\n                dim0 = 2;\r\n                dim1 = 0;\r\n            }\r\n            else //use x plane\r\n            {\r\n                plane = 0;\r\n                dim0 = 1;\r\n                dim1 = 2;\r\n            }\r\n        }\r\n        else if (abs(normal[2]) > abs(normal[0])) //use z plane\r\n        {\r\n            plane = 2;\r\n            dim0 = 0;\r\n            dim1 = 1;\r\n        }\r\n        else //use x plane\r\n        {\r\n            plane = 0;\r\n            dim0 = 1;\r\n            dim1 = 2;\r\n        }\r\n\r\n        clockness = (normal[plane] > 0.0f)? 1.0f : -1.0f;\r\n\r\n        float det0, det1, det2;\r\n\r\n        det0 = (point[dim0] - v[0][dim0]) * (v[0][dim1] - v[1][dim1]) +\r\n               (v[0][dim1] - point[dim1]) * (v[0][dim0] - v[1][dim0]);\r\n\r\n        det1 = (point[dim0] - v[1][dim0]) * (v[1][dim1] - v[2][dim1]) +\r\n               (v[1][dim1] - point[dim1]) * (v[1][dim0] - v[2][dim0]);\r\n\r\n        det2 = (point[dim0] - v[2][dim0]) * (v[2][dim1] - v[0][dim1]) +\r\n               (v[2][dim1] - point[dim1]) * (v[2][dim0] - v[0][dim0]);\r\n\r\n        int ret;\r\n\r\n        if (det0 > 0.0f)\r\n        {\r\n            if (det1 > 0.0f)\r\n            {\r\n                if (det2 > 0.0f)\r\n                    ret = -1; // inside\r\n                else\r\n                    ret = 5; // outside edge 2\r\n            }\r\n            else\r\n            {\r\n                if (det2 > 0.0f)\r\n                    ret = 3; // outside edge 1\r\n                else\r\n                    ret = 4; // outside vertex 2\r\n            }\r\n        }\r\n        else\r\n        {\r\n            if (det1 > 0.0f)\r\n            {\r\n                if (det2 > 0.0f)\r\n                    ret = 1; // outside edge 0\r\n                else\r\n                    ret = 0; // outside vertex 0\r\n            }\r\n            else\r\n            {\r\n                if (det2 > 0.0f)\r\n                    ret = 2; // outside vertex 1\r\n                else\r\n                    ret = -1; // inside\r\n            }\r\n        }\r\n\r\n        if (ret == -1)\r\n            return ret;\r\n\r\n        if (clockness == -1.0f)\r\n            ret = (ret + 3) % 6;\r\n\r\n        return ret;\r\n    }\r\n\r\n    AABB boundingBox()\r\n    {\r\n        Vector3f pmin = v[0];\r\n        Vector3f pmax = pmin;\r\n\r\n        void adjustMinPoint(Vector3f p)\r\n        {\r\n            if (p.x < pmin.x) pmin.x = p.x;\r\n            if (p.y < pmin.y) pmin.y = p.y;\r\n            if (p.z < pmin.z) pmin.z = p.z;\r\n        }\r\n\r\n        void adjustMaxPoint(Vector3f p)\r\n        {\r\n            if (p.x > pmax.x) pmax.x = p.x;\r\n            if (p.y > pmax.y) pmax.y = p.y;\r\n            if (p.z > pmax.z) pmax.z = p.z;\r\n        }\r\n\r\n        foreach(vertex; v)\r\n        {\r\n            adjustMinPoint(vertex);\r\n            adjustMaxPoint(vertex);\r\n        }\r\n\r\n        return boxFromMinMaxPoints(pmin, pmax);\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Triangle tri = {\r\n        v: [Vector3f(0, 0, 0), Vector3f(0, 1, 0), Vector3f(1, 0, 0)],\r\n        n: [Vector3f(0, 0, 1), Vector3f(0, 0, 1), Vector3f(0, 0, 1)],\r\n        normal: Vector3f(0, 0, 1)\r\n    };\r\n    \r\n    assert(tri.isPointInside(Vector3f(0.5f, 0.5f, 0.0f)) == -1);\r\n    assert(tri.isPointInside(Vector3f(-0.5f, 0.5f, 0.0f)) != -1);\r\n    \r\n    AABB aabb = tri.boundingBox();\r\n    assert(aabb.center == Vector3f(0.5f, 0.5f, 0.0f));\r\n    assert(aabb.size == Vector3f(0.5f, 0.5f, 0.0f));\r\n}\r\n"
  },
  {
    "path": "dlib/geometry/trimesh.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.geometry.trimesh;\r\n\r\nimport std.stdio;\r\nimport std.math;\r\n\r\nimport dlib.core.memory;\r\nimport dlib.container.array;\r\nimport dlib.math.vector;\r\nimport dlib.geometry.triangle;\r\n\r\nstruct Index\r\n{\r\n    uint a, b, c;\r\n}\r\n\r\nstruct FaceGroup\r\n{\r\n    Array!Index indicesArray;\r\n    int materialIndex;\r\n\r\n    @property Index[] indices() { return indicesArray.data; }\r\n\r\n    void addFace(uint a, uint b, uint c)\r\n    {\r\n        indicesArray.insertBack(Index(a, b, c));\r\n    }\r\n\r\n    void addFaces(Index[] inds)\r\n    {\r\n        indicesArray.insertBack(inds);\r\n    }\r\n}\r\n\r\n/// Triangle mesh\r\nclass TriMesh\r\n{\r\n    Array!(Vector3f) verticesArray;\r\n    Array!(Vector3f) normalsArray;\r\n    Array!(Vector3f) tangentsArray;\r\n    Array!(Vector2f) texcoordsArray;\r\n\r\n    Array!FaceGroup facegroupsArray;\r\n\r\n    @property Vector3f[] vertices()   { return verticesArray.data; }\r\n    @property Vector3f[] normals()    { return normalsArray.data; }\r\n    @property Vector3f[] tangents()   { return tangentsArray.data; }\r\n    @property Vector2f[] texcoords()  { return texcoordsArray.data; }\r\n    @property FaceGroup[] facegroups() { return facegroupsArray.data; }\r\n\r\n    this()\r\n    {\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        verticesArray.free();\r\n        normalsArray.free();\r\n        tangentsArray.free();\r\n        texcoordsArray.free();\r\n\r\n        foreach(fc; facegroupsArray)\r\n            fc.indicesArray.free();\r\n        facegroupsArray.free();\r\n    }\r\n\r\n    void addVertex(Vector3f v)\r\n    {\r\n        verticesArray.insertBack(v);\r\n    }\r\n\r\n    void addNormal(Vector3f n)\r\n    {\r\n        normalsArray.insertBack(n);\r\n    }\r\n\r\n    void addTangent(Vector3f t)\r\n    {\r\n        tangentsArray.insertBack(t);\r\n    }\r\n\r\n    void addTexcoord(Vector2f t)\r\n    {\r\n        texcoordsArray.insertBack(t);\r\n    }\r\n\r\n    void addVertices(Vector3f[] verts)\r\n    {\r\n        verticesArray.insertBack(verts);\r\n    }\r\n\r\n    void addNormals(Vector3f[] norms)\r\n    {\r\n        normalsArray.insertBack(norms);\r\n    }\r\n\r\n    void addTangents(Vector3f[] tans)\r\n    {\r\n        tangentsArray.insertBack(tans);\r\n    }\r\n\r\n    void addTexcoords(Vector2f[] texs)\r\n    {\r\n        texcoordsArray.insertBack(texs);\r\n    }\r\n\r\n    FaceGroup* addFacegroup()\r\n    {\r\n        FaceGroup fg;\r\n        facegroupsArray.insertBack(fg);\r\n        return &facegroupsArray.data[$-1];\r\n    }\r\n\r\n    Triangle getTriangle(uint facegroupIndex, uint triIndex)\r\n    {\r\n        Triangle tri;\r\n        Index triIdx = facegroupsArray[facegroupIndex].indicesArray[triIndex];\r\n\r\n        tri.v[0] = verticesArray[triIdx.a];\r\n        tri.v[1] = verticesArray[triIdx.b];\r\n        tri.v[2] = verticesArray[triIdx.c];\r\n\r\n        tri.n[0] = normalsArray[triIdx.a];\r\n        tri.n[1] = normalsArray[triIdx.b];\r\n        tri.n[2] = normalsArray[triIdx.c];\r\n\r\n        if (texcoordsArray.length == verticesArray.length)\r\n        {\r\n            tri.t1[0] = texcoordsArray[triIdx.a];\r\n            tri.t1[1] = texcoordsArray[triIdx.b];\r\n            tri.t1[2] = texcoordsArray[triIdx.c];\r\n        }\r\n\r\n        tri.normal = planeNormal(tri.v[0], tri.v[1], tri.v[2]);\r\n\r\n        tri.barycenter = (tri.v[0] + tri.v[1] + tri.v[2]) / 3;\r\n\r\n        tri.d = (tri.v[0].x * tri.normal.x +\r\n                 tri.v[0].y * tri.normal.y +\r\n                 tri.v[0].z * tri.normal.z);\r\n\r\n        tri.edges[0] = tri.v[1] - tri.v[0];\r\n        tri.edges[1] = tri.v[2] - tri.v[1];\r\n        tri.edges[2] = tri.v[0] - tri.v[2];\r\n\r\n        tri.materialIndex = facegroupsArray[facegroupIndex].materialIndex;\r\n\r\n        return tri;\r\n    }\r\n\r\n    /**\r\n     * Read-only triangle aggregate\r\n     */\r\n    int opApply(scope int delegate(ref Triangle) dg)\r\n    {\r\n        int result = 0;\r\n        for (uint fgi = 0; fgi < facegroupsArray.length; fgi++)\r\n        for (uint i = 0; i < facegroupsArray[fgi].indices.length; i++)\r\n        {\r\n            Triangle tri = getTriangle(fgi, i);\r\n            result = dg(tri);\r\n            if (result)\r\n                break;\r\n        }\r\n        return result;\r\n    }\r\n\r\n    void genTangents()\r\n    {\r\n        tangentsArray.free();\r\n\r\n        Vector3f[] sTan = New!(Vector3f[])(verticesArray.length);\r\n        Vector3f[] tTan = New!(Vector3f[])(verticesArray.length);\r\n\r\n        foreach(i, v; sTan)\r\n        {\r\n            sTan[i] = Vector3f(0.0f, 0.0f, 0.0f);\r\n            tTan[i] = Vector3f(0.0f, 0.0f, 0.0f);\r\n        }\r\n\r\n        foreach(ref fg; facegroupsArray)\r\n        foreach(ref index; fg.indicesArray)\r\n        {\r\n            uint i0 = index.a;\r\n            uint i1 = index.b;\r\n            uint i2 = index.c;\r\n\r\n            Vector3f v0 = verticesArray[i0];\r\n            Vector3f v1 = verticesArray[i1];\r\n            Vector3f v2 = verticesArray[i2];\r\n\r\n            Vector2f w0 = texcoordsArray[i0];\r\n            Vector2f w1 = texcoordsArray[i1];\r\n            Vector2f w2 = texcoordsArray[i2];\r\n\r\n            float x1 = v1.x - v0.x;\r\n            float x2 = v2.x - v0.x;\r\n            float y1 = v1.y - v0.y;\r\n            float y2 = v2.y - v0.y;\r\n            float z1 = v1.z - v0.z;\r\n            float z2 = v2.z - v0.z;\r\n\r\n            float s1 = w1[0] - w0[0];\r\n            float s2 = w2[0] - w0[0];\r\n            float t1 = w1[1] - w0[1];\r\n            float t2 = w2[1] - w0[1];\r\n\r\n            float r = (s1 * t2) - (s2 * t1);\r\n\r\n            // Prevent division by zero\r\n            if (r == 0.0f)\r\n                r = 1.0f;\r\n\r\n            float oneOverR = 1.0f / r;\r\n\r\n            Vector3f sDir = Vector3f((t2 * x1 - t1 * x2) * oneOverR,\r\n                                     (t2 * y1 - t1 * y2) * oneOverR,\r\n                                     (t2 * z1 - t1 * z2) * oneOverR);\r\n            Vector3f tDir = Vector3f((s1 * x2 - s2 * x1) * oneOverR,\r\n                                     (s1 * y2 - s2 * y1) * oneOverR,\r\n                                     (s1 * z2 - s2 * z1) * oneOverR);\r\n\r\n            sTan[i0] += sDir;\r\n            tTan[i0] += tDir;\r\n\r\n            sTan[i1] += sDir;\r\n            tTan[i1] += tDir;\r\n\r\n            sTan[i2] += sDir;\r\n            tTan[i2] += tDir;\r\n        }\r\n\r\n        tangentsArray.resize(vertices.length, Vector3f(0.0f, 0.0f, 0.0f));\r\n\r\n        // Calculate vertex tangent\r\n        foreach(i, v; tangents)\r\n        {\r\n            Vector3f n = normalsArray[i];\r\n            Vector3f t = sTan[i];\r\n\r\n            // Gram-Schmidt orthogonalize\r\n            Vector3f tangent = (t - n * dot(n, t));\r\n            tangent.normalize();\r\n            tangentsArray[i] = tangent;\r\n        }\r\n\r\n        Delete(sTan);\r\n        Delete(tTan);\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/geometry/utils.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.geometry.utils;\r\n\r\nimport dlib.math.vector;\r\n\r\nVector3f triBarycentricCoords(Vector3f v0, Vector3f v1, Vector3f v2, Vector3f p)\r\n{\r\n    float triArea = cross((v1 - v0), (v2 - v0)).length * 0.5f;\r\n    float u = (cross((v1 - p ), (v2 - p)).length * 0.5f) / triArea;\r\n    float v = (cross((v0 - p ), (v2 - p)).length * 0.5f) / triArea;\r\n    float w = (cross((v0 - p ), (v1 - p)).length * 0.5f) / triArea;\r\n    return Vector3f(u, v, w);\r\n}\r\n\r\n// ta, tb, tc - triangle texture coords\r\n// s, t - coords in texture space\r\nVector3f triBarycentricCoords(Vector2f ta, Vector2f tb, Vector2f tc, float s, float t)\r\n{\r\n    float d = (tb.x * tc.y) - (tb.y * tc.x) - (ta.x * tc.y) + (ta.y * tc.x) + (ta.x * tb.y) - (ta.y * tb.x);\r\n\r\n    float m1 = ((tb.x * tc.y) - (tb.y * tc.x) - (s * tc.y) + (t * tc.x) + (s * tb.y) - (t * tb.x)) / d;\r\n    float m2 = ((s * tc.y) - (t * tc.x) - (ta.x * tc.y) + (ta.y * tc.x) + (ta.x * t) - (ta.y * s)) / d;\r\n    float m3 = ((tb.x * t) - (tb.y * s) - (ta.x * t) + (ta.y * s) + (ta.x * tb.y) - (ta.y * tb.x)) / d;\r\n\r\n    return Vector3f(m1, m2, m3);\r\n}\r\n\r\n// va, vb, vc - triangle vectors (vertices, normals, colors, etc)\r\n// bcc - barycentric coords\r\nVector3f triTextureSpaceToObjectSpace(Vector3f va, Vector3f vb, Vector3f vc, Vector3f bcc)\r\n{\r\n    return Vector3f(\r\n        va.x * bcc.x + vb.x * bcc.y + vc.x * bcc.z,\r\n        va.y * bcc.x + vb.y * bcc.y + vc.y * bcc.z,\r\n        va.z * bcc.x + vb.z * bcc.y + vc.z * bcc.z,\r\n    );\r\n}\r\n\r\nVector2f triObjectSpaceToTextureSpace(\r\n    Vector3f p1, Vector3f p2, Vector3f p3,\r\n    Vector2f uv1, Vector2f uv2, Vector2f uv3,\r\n    Vector3f pos)\r\n{\r\n    // Compute vectors\r\n    Vector3f v0 = p3 - p1;\r\n    Vector3f v1 = p2 - p1;\r\n    Vector3f v2 = pos - p1;\r\n\r\n    // Compute dot products\r\n    float dot00 = dot(v0, v0);\r\n    float dot01 = dot(v0, v1);\r\n    float dot02 = dot(v0, v2);\r\n    float dot11 = dot(v1, v1);\r\n    float dot12 = dot(v1, v2);\r\n\r\n    // Compute barycentric coordinates\r\n    float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01);\r\n    float u = (dot11 * dot02 - dot01 * dot12) * invDenom;\r\n    float v = (dot00 * dot12 - dot01 * dot02) * invDenom;\r\n\r\n    Vector2f t2 = uv2 - uv1;\r\n    Vector2f t1 = uv3 - uv1;\r\n\r\n    return uv1 + t1*u + t2*v;\r\n}\r\n\r\nfloat sign(Vector2f p1, Vector2f p2, Vector2f p3)\r\n{\r\n    return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);\r\n}\r\n\r\nbool isPointInTriangle2D(Vector2f pt, Vector2f v1, Vector2f v2, Vector2f v3)\r\n{\r\n    bool b1, b2, b3;\r\n\r\n    b1 = sign(pt, v1, v2) < 0.0f;\r\n    b2 = sign(pt, v2, v3) < 0.0f;\r\n    b3 = sign(pt, v3, v1) < 0.0f;\r\n\r\n    return ((b1 == b2) && (b2 == b3));\r\n}\r\n"
  },
  {
    "path": "dlib/image/animation.d",
    "content": "/*\r\nCopyright (c) 2017-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Animated images\r\n *\r\n * Copyright: Timur Gafarov 2017-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.animation;\r\n\r\nimport dlib.core.memory;\r\nimport dlib.image.image;\r\n\r\n/**\r\n * Animated image interface \r\n */\r\ninterface SuperAnimatedImage: SuperImage\r\n{\r\n    @property size_t frameSize();\r\n    @property uint numFrames();\r\n    @property uint currentFrame();\r\n    @property void currentFrame(uint f);\r\n    uint advanceFrame();\r\n}\r\n\r\n/**\r\n * Extension of standard Image that handles animation\r\n *\r\n * Description:\r\n * AnimatedImage can store more than one frame of pixel data.\r\n * Current frame can be switched with currentFrame property.\r\n * All usual image operations work on current frame, so\r\n * you can use this class with any existing dlib.image\r\n * functionality: save to file, apply filters, etc.\r\n */\r\nclass AnimatedImage(IntegerPixelFormat fmt): Image!(fmt), SuperAnimatedImage\r\n{\r\n    protected:\r\n    uint _numFrames;\r\n    uint _currentFrame = 0;\r\n    size_t _frameSize;\r\n\r\n    public:\r\n    this(uint w, uint h, uint frames)\r\n    {\r\n        _numFrames = frames;\r\n        super(w, h);\r\n        _frameSize = _width * _height * _pixelSize;\r\n    }\r\n\r\n    override @property SuperImage dup()\r\n    {\r\n        auto res = new AnimatedImage!(fmt)(_width, _height, _numFrames);\r\n        res.data[] = data[];\r\n        return res;\r\n    }\r\n\r\n    override SuperImage createSameFormat(uint w, uint h)\r\n    {\r\n        return new AnimatedImage!(fmt)(w, h, _numFrames);\r\n    }\r\n\r\n    @property size_t frameSize()\r\n    {\r\n        return _frameSize;\r\n    }\r\n\r\n    @property uint numFrames()\r\n    {\r\n        return _numFrames;\r\n    }\r\n\r\n    @property uint currentFrame()\r\n    {\r\n        return _currentFrame;\r\n    }\r\n\r\n    @property void currentFrame(uint f)\r\n    {\r\n        if (f >= _numFrames)\r\n            _currentFrame = _numFrames - 1;\r\n        else\r\n            _currentFrame = f;\r\n    }\r\n\r\n    uint advanceFrame()\r\n    {\r\n        currentFrame = currentFrame + 1;\r\n        return _currentFrame;\r\n    }\r\n\r\n    override @property ubyte[] data()\r\n    {\r\n        if (_currentFrame >= _numFrames)\r\n            _currentFrame = _numFrames - 1;\r\n        size_t offset = _frameSize * _currentFrame;\r\n        return _data[offset..offset+_frameSize];\r\n    }\r\n\r\n    protected override void allocateData()\r\n    {\r\n        _data = new ubyte[_width * _height * _pixelSize * _numFrames];\r\n    }\r\n}\r\n\r\n/// Specialization of AnimatedImage for 8-bit luminance pixel format\r\nalias AnimatedImageL8 = AnimatedImage!(IntegerPixelFormat.L8);\r\n/// Specialization of AnimatedImage for 8-bit luminance-alpha pixel format\r\nalias AnimatedImageLA8 = AnimatedImage!(IntegerPixelFormat.LA8);\r\n/// Specialization of AnimatedImage for 8-bit RGB pixel format\r\nalias AnimatedImageRGB8 = AnimatedImage!(IntegerPixelFormat.RGB8);\r\n/// Specialization of AnimatedImage for 8-bit RGBA pixel format\r\nalias AnimatedImageRGBA8 = AnimatedImage!(IntegerPixelFormat.RGBA8);\r\n\r\n/// Specialization of AnimatedImage for 16-bit luminance pixel format\r\nalias AnimatedImageL16 = AnimatedImage!(IntegerPixelFormat.L16);\r\n/// Specialization of AnimatedImage for 16-bit luminance-alpha pixel format\r\nalias AnimatedImageLA16 = AnimatedImage!(IntegerPixelFormat.LA16);\r\n/// Specialization of AnimatedImage for 16-bit RGB pixel format\r\nalias AnimatedImageRGB16 = AnimatedImage!(IntegerPixelFormat.RGB16);\r\n/// Specialization of AnimatedImage for 16-bit RGBA pixel format\r\nalias AnimatedImageRGBA16 = AnimatedImage!(IntegerPixelFormat.RGBA16);\r\n\r\n/**\r\n * Factory class for animated images\r\n */\r\nclass AnimatedImageFactory: SuperImageFactory\r\n{\r\n    SuperImage createImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1)\r\n    {\r\n        return animatedImage(w, h, channels, bitDepth, numFrames);\r\n    }\r\n}\r\n\r\nprivate AnimatedImageFactory _defaultAnimatedImageFactory;\r\n\r\n/**\r\n * Get default AnimatedImageFactory singleton\r\n */\r\nAnimatedImageFactory animatedImageFactory()\r\n{\r\n    if (!_defaultAnimatedImageFactory)\r\n        _defaultAnimatedImageFactory = new AnimatedImageFactory();\r\n    return _defaultAnimatedImageFactory;\r\n}\r\n\r\n/**\r\n * Factory function for animated images\r\n */\r\nSuperImage animatedImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1)\r\nin\r\n{\r\n    assert(channels > 0 && channels <= 4);\r\n    assert(bitDepth == 8 || bitDepth == 16);\r\n}\r\ndo\r\n{\r\n    switch(channels)\r\n    {\r\n        case 1:\r\n        {\r\n            if (bitDepth == 8)\r\n                return new AnimatedImageL8(w, h, numFrames);\r\n            else\r\n                return new AnimatedImageL16(w, h, numFrames);\r\n        }\r\n        case 2:\r\n        {\r\n            if (bitDepth == 8)\r\n                return new AnimatedImageLA8(w, h, numFrames);\r\n            else\r\n                return new AnimatedImageLA16(w, h, numFrames);\r\n        }\r\n        case 3:\r\n        {\r\n            if (bitDepth == 8)\r\n                return new AnimatedImageRGB8(w, h, numFrames);\r\n            else\r\n                return new AnimatedImageRGB16(w, h, numFrames);\r\n        }\r\n        case 4:\r\n        {\r\n            if (bitDepth == 8)\r\n                return new AnimatedImageRGBA8(w, h, numFrames);\r\n            else\r\n                return new AnimatedImageRGBA16(w, h, numFrames);\r\n        }\r\n        default:\r\n            assert(0);\r\n    }\r\n}\r\n\r\n/**\r\n * AnimatedImage that uses dlib.core.memory instead of GC\r\n */\r\nclass UnmanagedAnimatedImage(IntegerPixelFormat fmt): AnimatedImage!(fmt)\r\n{\r\n    override @property SuperImage dup()\r\n    {\r\n        auto res = New!(UnmanagedAnimatedImage!(fmt))(_width, _height, _numFrames);\r\n        res.data[] = data[];\r\n        return res;\r\n    }\r\n\r\n    override SuperImage createSameFormat(uint w, uint h)\r\n    {\r\n        return New!(UnmanagedAnimatedImage!(fmt))(w, h, _numFrames);\r\n    }\r\n\r\n    this(uint w, uint h, uint frames)\r\n    {\r\n        super(w, h, frames);\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        Delete(_data);\r\n    }\r\n\r\n    protected override void allocateData()\r\n    {\r\n        _data = New!(ubyte[])(_width * _height * _pixelSize * _numFrames);\r\n    }\r\n\r\n    override void free()\r\n    {\r\n        Delete(this);\r\n    }\r\n}\r\n\r\n/// Specialization of UnmanagedAnimatedImage for 8-bit luminance pixel format\r\nalias UnmanagedAnimatedImageL8 = UnmanagedAnimatedImage!(IntegerPixelFormat.L8);\r\n/// Specialization of UnmanagedAnimatedImage for 8-bit luminance-alpha pixel format\r\nalias UnmanagedAnimatedImageLA8 = UnmanagedAnimatedImage!(IntegerPixelFormat.LA8);\r\n/// Specialization of UnmanagedAnimatedImage for 8-bit RGB pixel format\r\nalias UnmanagedAnimatedImageRGB8 = UnmanagedAnimatedImage!(IntegerPixelFormat.RGB8);\r\n/// Specialization of UnmanagedAnimatedImage for 8-bit RGBA pixel format\r\nalias UnmanagedAnimatedImageRGBA8 = UnmanagedAnimatedImage!(IntegerPixelFormat.RGBA8);\r\n\r\n/// Specialization of UnmanagedAnimatedImage for 16-bit luminance pixel format\r\nalias UnmanagedAnimatedImageL16 = UnmanagedAnimatedImage!(IntegerPixelFormat.L16);\r\n/// Specialization of UnmanagedAnimatedImage for 16-bit luminance-alpha pixel format\r\nalias UnmanagedAnimatedImageLA16 = UnmanagedAnimatedImage!(IntegerPixelFormat.LA16);\r\n/// Specialization of UnmanagedAnimatedImage for 16-bit RGB pixel format\r\nalias UnmanagedAnimatedImageRGB16 = UnmanagedAnimatedImage!(IntegerPixelFormat.RGB16);\r\n/// Specialization of UnmanagedAnimatedImage for 16-bit RGBA pixel format\r\nalias UnmanagedAnimatedImageRGBA16 = UnmanagedAnimatedImage!(IntegerPixelFormat.RGBA16);\r\n\r\n/**\r\n * Factory class for UnmanagedAnimatedImage\r\n */\r\nclass UnmanagedAnimatedImageFactory: SuperImageFactory\r\n{\r\n    SuperImage createImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1)\r\n    {\r\n        return unmanagedAnimatedImage(w, h, channels, bitDepth, numFrames);\r\n    }\r\n}\r\n\r\n/**\r\n * Factory function for UnmanagedAnimatedImage\r\n */\r\nSuperImage unmanagedAnimatedImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1)\r\nin\r\n{\r\n    assert(channels > 0 && channels <= 4);\r\n    assert(bitDepth == 8 || bitDepth == 16);\r\n}\r\ndo\r\n{\r\n    switch(channels)\r\n    {\r\n        case 1:\r\n        {\r\n            if (bitDepth == 8)\r\n                return New!UnmanagedAnimatedImageL8(w, h, numFrames);\r\n            else\r\n                return New!UnmanagedAnimatedImageL16(w, h, numFrames);\r\n        }\r\n        case 2:\r\n        {\r\n            if (bitDepth == 8)\r\n                return New!UnmanagedAnimatedImageLA8(w, h, numFrames);\r\n            else\r\n                return New!UnmanagedAnimatedImageLA16(w, h, numFrames);\r\n        }\r\n        case 3:\r\n        {\r\n            if (bitDepth == 8)\r\n                return New!UnmanagedAnimatedImageRGB8(w, h, numFrames);\r\n            else\r\n                return New!UnmanagedAnimatedImageRGB16(w, h, numFrames);\r\n        }\r\n        case 4:\r\n        {\r\n            if (bitDepth == 8)\r\n                return New!UnmanagedAnimatedImageRGBA8(w, h, numFrames);\r\n            else\r\n                return New!UnmanagedAnimatedImageRGBA16(w, h, numFrames);\r\n        }\r\n        default:\r\n            assert(0);\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/image/arithmetics.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Per-pixel image arithmetics\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.arithmetics;\r\n\r\nimport dlib.image.image;\r\nimport dlib.image.color;\r\n\r\n/// Add two images\r\nSuperImage add(SuperImage a, SuperImage b, SuperImage outp, float t = 1.0f)\r\nin\r\n{\r\n    assert(a.width == b.width);\r\n    assert(a.height == b.height);\r\n}\r\ndo\r\n{\r\n    SuperImage img;\r\n    if (outp)\r\n        img = outp;\r\n    else\r\n        img = a.dup;\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        Color4f acol = Color4f(a[x, y]);\r\n        Color4f bcol = Color4f(b[x, y]);\r\n        Color4f col  = acol + (bcol * t);\r\n        col.a = acol.a;\r\n        img[x, y] = col;\r\n    }\r\n\r\n    return img;\r\n}\r\n\r\n/// ditto\r\nSuperImage add(SuperImage a, SuperImage b, float t = 1.0f)\r\n{\r\n    return add(a, b, null, t);\r\n}\r\n\r\n/// Subtract image b from image a\r\nSuperImage subtract(SuperImage a, SuperImage b, SuperImage outp, float t = 1.0f)\r\nin\r\n{\r\n    assert(a.width == b.width);\r\n    assert(a.height == b.height);\r\n}\r\ndo\r\n{\r\n    SuperImage img;\r\n    if (outp)\r\n        img = outp;\r\n    else\r\n        img = a.dup;\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        Color4f acol = Color4f(a[x, y]);\r\n        Color4f bcol = Color4f(b[x, y]);\r\n        Color4f col  = acol - (bcol * t);\r\n        col.a = acol.a;\r\n        img[x, y] = col;\r\n    }\r\n\r\n    return img;\r\n}\r\n\r\n/// ditto\r\nSuperImage subtract(SuperImage a, SuperImage b, float t = 1.0f)\r\n{\r\n    return subtract(a, b, null, t);\r\n}\r\n\r\n/// Multiply two images\r\nSuperImage multiply(SuperImage a, SuperImage b, SuperImage outp, float t = 1.0f)\r\nin\r\n{\r\n    assert(a.width == b.width);\r\n    assert(a.height == b.height);\r\n}\r\ndo\r\n{\r\n    SuperImage img;\r\n    if (outp)\r\n        img = outp;\r\n    else\r\n        img = a.dup;\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        Color4f acol = Color4f(a[x, y]);\r\n        Color4f bcol = Color4f(b[x, y]);\r\n        Color4f col  = acol * (bcol * t);\r\n        col.a = acol.a;\r\n        img[x, y] = col;\r\n    }\r\n\r\n    return img;\r\n}\r\n\r\n/// ditto\r\nSuperImage multiply(SuperImage a, SuperImage b, float t = 1.0f)\r\n{\r\n    return multiply(a, b, null, t);\r\n}\r\n\r\n/// Divide two images\r\nSuperImage divide(SuperImage a, SuperImage b, SuperImage outp, float t = 1.0f)\r\nin\r\n{\r\n    assert(a.width == b.width);\r\n    assert(a.height == b.height);\r\n}\r\ndo\r\n{\r\n    SuperImage img;\r\n    if (outp)\r\n        img = outp;\r\n    else\r\n        img = a.dup;\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        Color4f acol = Color4f(a[x, y]);\r\n        Color4f bcol = Color4f(b[x, y]);\r\n        Color4f col  = acol / (bcol * t);\r\n        col.a = acol.a;\r\n        img[x, y] = col;\r\n    }\r\n\r\n    return img;\r\n}\r\n\r\n/// ditto\r\nSuperImage divide(SuperImage a, SuperImage b, float t = 1.0f)\r\n{\r\n    return divide(a, b, null, t);\r\n}\r\n\r\n/// Invert image\r\nSuperImage invert(SuperImage a, SuperImage outp)\r\n{\r\n    SuperImage img;\r\n    if (outp)\r\n        img = outp;\r\n    else\r\n        img = a.dup;\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        img[x, y] = a[x, y].inverse;\r\n    }\r\n\r\n    return img;\r\n}\r\n\r\n/// ditto\r\nSuperImage invert(SuperImage a)\r\n{\r\n    return invert(a, null);\r\n}\r\n"
  },
  {
    "path": "dlib/image/canvas.d",
    "content": "/*\nCopyright (c) 2018-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Simple 2D rendering engine\n *\n * Copyright: Timur Gafarov 2018-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.image.canvas;\n\nimport std.math;\n\nimport dlib.math.vector;\nimport dlib.math.matrix;\nimport dlib.math.transformation;\nimport dlib.math.utils;\nimport dlib.math.interpolation.bezier;\nimport dlib.container.array;\nimport dlib.image.color;\nimport dlib.image.image;\nimport dlib.image.render.shapes;\nimport dlib.core.memory;\n\n/// General options for drawing things\nstruct CanvasState\n{\n    Matrix3x3f transformation;\n    Color4f lineColor;\n    Color4f fillColor;\n    float lineWidth;\n}\n\n/// Type of a path segment\nenum SegmentType\n{\n    Line,\n    BezierCubic\n}\n\n/// Path segment\nstruct ContourSegment\n{\n    Vector2f p1;\n    Vector2f p2;\n    Vector2f p3;\n    Vector2f p4;\n    float radius;\n    SegmentType type;\n}\n\n/**\n * A simple 2D vector engine inspired by HTML5 canvas.\n * Supports rendering arbitrary polygons and cubic Bezier paths, filled and outlined.\n * Not real-time.\n */\nclass Canvas\n{\n   protected:\n    SuperImage _image;\n    SuperImage tmpBuffer;\n\n    // TODO: state stack\n    CanvasState state;\n\n    Array!ContourSegment contour;\n    Vector2f penPosition;\n\n    float tesselationStep = 1.0f / 40.0f;\n    uint subpixelResolution = 4;\n\n   public:\n    this(SuperImage img)\n    {\n        _image = img;\n\n        tmpBuffer = image.createSameFormat(_image.width, _image.height);\n\n        state.transformation = Matrix3x3f.identity;\n        state.lineColor = Color4f(0.0f, 0.0f, 0.0f, 1.0f);\n        state.fillColor = Color4f(0.0f, 0.0f, 0.0f, 1.0f);\n        state.lineWidth = 1.0f;\n\n        penPosition = Vector2f(0.0f, 0.0f);\n    }\n\n    ~this()\n    {\n        contour.free();\n        tmpBuffer.free();\n    }\n\n   public:\n    SuperImage image() @property\n    {\n        return _image;\n    }\n\n    void fillColor(Color4f c) @property\n    {\n        state.fillColor = c;\n    }\n\n    Color4f fillColor() @property\n    {\n        return state.fillColor;\n    }\n\n    void lineColor(Color4f c) @property\n    {\n        state.lineColor = c;\n    }\n\n    Color4f lineColor() @property\n    {\n        return state.lineColor;\n    }\n\n    void lineWidth(float w) @property\n    {\n        state.lineWidth = w;\n    }\n\n    float lineWidth() @property\n    {\n        return state.lineWidth;\n    }\n\n    void resetTransform()\n    {\n        state.transformation = Matrix3x3f.identity;\n    }\n\n    void transform(Matrix3x3f m)\n    {\n        state.transformation *= m;\n    }\n\n    void translate(float x, float y)\n    {\n        state.transformation *= translationMatrix2D(Vector2f(x, y));\n    }\n\n    void rotate(float a)\n    {\n        state.transformation *= rotationMatrix2D(a);\n    }\n\n    void scale(float x, float y)\n    {\n        state.transformation *= scaleMatrix2D(Vector2f(x, y));\n    }\n\n    void clear(Color4f c)\n    {\n        dlib.image.render.shapes.fillColor(_image, c);\n    }\n\n    void beginPath()\n    {\n        penPosition = Vector2f(0.0f, 0.0f);\n    }\n\n    void endPath()\n    {\n        contour.free();\n    }\n\n    void pathMoveTo(float x, float y)\n    {\n        penPosition = Vector2f(x, y);\n    }\n\n    void pathLineTo(float x, float y)\n    {\n        Vector2f p1 = penPosition;\n        Vector2f p2 = Vector2f(x, y);\n        pathAddLine(p1, p2);\n        penPosition = p2;\n    }\n\n    void pathBezierTo(Vector2f cp1, Vector2f cp2, Vector2f endPoint)\n    {\n        pathAddBezierCubic(penPosition, cp1, cp2, endPoint);\n        penPosition = endPoint;\n    }\n\n    void pathStroke()\n    {\n        dlib.image.render.shapes.fillColor(tmpBuffer, Color4f(0, 0, 0, 0));\n        drawContour();\n        blitTmpBuffer(state.lineColor);\n    }\n\n    void pathFill()\n    {\n        fillShape();\n    }\n\n   protected:\n    void pathAddLine(Vector2f p1, Vector2f p2)\n    {\n        ContourSegment segment;\n        segment.p1 = p1;\n        segment.p2 = p2;\n        segment.type = SegmentType.Line;\n        contour.append(segment);\n    }\n\n    void pathAddBezierCubic(Vector2f p1, Vector2f p2, Vector2f p3, Vector2f p4)\n    {\n        ContourSegment segment;\n        segment.p1 = p1;\n        segment.p2 = p2;\n        segment.p3 = p3;\n        segment.p4 = p4;\n        segment.type = SegmentType.BezierCubic;\n        contour.append(segment);\n    }\n\n    void fillShape()\n    {\n        Array!Vector2f poly;\n        Array!size_t polyBounds;\n        Vector2f startP, endP, wayP1, wayP2;\n\n        foreach(ref p; contour.data)\n        {\n            startP = p.p1.affineTransform2D(state.transformation);\n\n            if (startP != endP)\n            {\n                polyBounds.append(poly.length);\n                poly.append(startP);\n            }\n\n            if (p.type == SegmentType.Line)\n            {\n                endP = p.p2.affineTransform2D(state.transformation);\n                poly.append(endP);\n            }\n            else\n            {\n                assert(p.type == SegmentType.BezierCubic);\n                wayP1 = p.p2.affineTransform2D(state.transformation);\n                wayP2 = p.p3.affineTransform2D(state.transformation);\n                endP = p.p4.affineTransform2D(state.transformation);\n\n                float t = 0.0f;\n                while(t < 1.0f)\n                {\n                    t += tesselationStep;\n                    Vector2f tessP = bezierVector2(startP, wayP1, wayP2, endP, t);\n                    poly.append(tessP);\n                }\n            }\n        }\n\n        auto alphas = New!(float[])(polyBounds.length);\n        polyBounds.append(poly.length);\n\n        foreach(y; 0.._image.height)\n        foreach(x; 0.._image.width)\n        {\n            auto p = Vector2f(x, y);\n            bool inside = false;\n\n            foreach(i, ref alpha; alphas)\n            {\n                alpha = pointInPolygonAAFast(p, poly.data[polyBounds[i] .. polyBounds[i+1]]);\n                if (alpha == 1) inside = !inside;\n            }\n\n            float totalAlpha = inside? 1: 0;\n\n            if (inside)\n            {\n                foreach(alpha; alphas)\n                {\n                    totalAlpha *= alpha == 1? 1: 1 - alpha;\n                }\n            }\n            else\n            {\n                foreach(alpha; alphas)\n                {\n                    totalAlpha += alpha == 1? 0: (1 - totalAlpha) * alpha;\n                }\n            }\n\n            Color4f c = state.fillColor;\n            c.a = c.a * totalAlpha;\n\n            if (c.a > 0.0f)\n            {\n                _image[x, y] = alphaOver(_image[x, y], c);\n            }\n        }\n\n        poly.free();\n        polyBounds.free();\n        alphas.Delete();\n    }\n\n    void drawContour()\n    {\n        Vector2f tp1, tp2, tp3, tp4;\n\n        foreach(i, ref p; contour.data)\n        {\n            if (p.type == SegmentType.Line)\n            {\n                tp1 = p.p1.affineTransform2D(state.transformation);\n                tp2 = p.p2.affineTransform2D(state.transformation);\n                drawLine(tp1, tp2);\n            }\n            else if (p.type == SegmentType.BezierCubic)\n            {\n                tp1 = p.p1.affineTransform2D(state.transformation);\n                tp2 = p.p2.affineTransform2D(state.transformation);\n                tp3 = p.p3.affineTransform2D(state.transformation);\n                tp4 = p.p4.affineTransform2D(state.transformation);\n                drawBezierCurve(tp1, tp2, tp3, tp4);\n            }\n        }\n    }\n\n    void drawLineTangent(Vector2f p1, Vector2f p2, Vector2f t1, Vector2f t2)\n    {\n        Vector2f n1 = Vector2f(-t1.y, t1.x);\n        Vector2f n2 = Vector2f(-t2.y, t2.x);\n\n        Vector2f offset1 = n1 * state.lineWidth * 0.5f;\n        Vector2f offset2 = n2 * state.lineWidth * 0.5f;\n\n        Vector2f[4] poly;\n        poly[0] = p1 - offset1;\n        poly[1] = p1 + offset1;\n        poly[2] = p2 + offset2;\n        poly[3] = p2 - offset2;\n\n        float subpSize = 1.0f / subpixelResolution;\n        float subpContrib = 1.0f / (subpixelResolution * subpixelResolution);\n\n        int xmin = cast(int)min2(min2(poly[0].x, poly[1].x), min2(poly[2].x, poly[3].x)) - 1;\n        int ymin = cast(int)min2(min2(poly[0].y, poly[1].y), min2(poly[2].y, poly[3].y)) - 1;\n        int xmax = cast(int)max2(max2(poly[0].x, poly[1].x), max2(poly[2].x, poly[3].x)) + 1;\n        int ymax = cast(int)max2(max2(poly[0].y, poly[1].y), max2(poly[2].y, poly[3].y)) + 1;\n\n        foreach(y; ymin..ymax)\n        foreach(x; xmin..xmax)\n        {\n            float alpha = 0.0f;\n\n            foreach(sy; 0..subpixelResolution)\n            foreach(sx; 0..subpixelResolution)\n            {\n                auto p = Vector2f(x + sx * subpSize, y + sy * subpSize);\n\n                if (pointInPolygon(p, poly))\n                    alpha += subpContrib;\n            }\n\n            float srcAlpha = tmpBuffer[x, y].r;\n            tmpBuffer[x, y] = Color4f(min2(srcAlpha + alpha, 1.0f), 0, 0, 1);\n        }\n    }\n\n    void drawLine(Vector2f p1, Vector2f p2)\n    {\n        Vector2f dir = p2 - p1;\n        Vector2f ndir = dir.normalized;\n        drawLineTangent(p1, p2, ndir, ndir);\n    }\n\n    void drawBezierCurve(Vector2f a, Vector2f b, Vector2f c, Vector2f d)\n    {\n        Vector2f p1 = a;\n        Vector2f t1 = bezierTangentVector2(a, b, c, d, 0.0f).normalized;\n\n        float t = 0.0f;\n        while(t < 1.0f)\n        {\n            t += tesselationStep;\n            Vector2f p2 = bezierVector2(a, b, c, d, t);\n            Vector2f t2 = bezierTangentVector2(a, b, c, d, t).normalized;\n            drawLineTangent(p1, p2, t1, t2);\n            p1 = p2;\n            t1 = t2;\n        }\n    }\n\n    void blitTmpBuffer(Color4f color)\n    {\n        foreach(y; 0.._image.height)\n        foreach(x; 0.._image.width)\n        {\n            Color4f c1 = _image[x, y];\n            Color4f c2 = color;\n            c2.a = tmpBuffer[x, y].r * color.a;\n            _image[x, y] = alphaOver(c1, c2);\n        }\n    }\n}\n\nbool pointInPolygon(Vector2f p, Vector2f[] poly)\n{\n    size_t i = 0;\n    size_t j = poly.length - 1;\n    bool inside = false;\n\n    for (i = 0; i < poly.length; i++)\n    {\n        Vector2f a = poly[i];\n        Vector2f b = poly[j];\n\n        if ((a.y > p.y) != (b.y > p.y) &&\n            (p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x))\n            inside = !inside;\n\n        j = i;\n    }\n\n    return inside;\n}\n\nfloat sqrDistanceToLineSegment(Vector2f a, Vector2f b, Vector2f p)\n{\n    Vector2f n = b - a;\n    Vector2f pa = a - p;\n\n    float c = dot(n, pa);\n\n    if (c > 0.0f)\n        return dot(pa, pa);\n\n    Vector2f bp = p - b;\n\n    if (dot(n, bp) > 0.0f)\n        return dot(bp, bp);\n\n    Vector2f e = pa - n * (c / dot(n, n));\n\n    return dot(e, e);\n}\n\nfloat pointInPolygonAAFast(Vector2f p, Vector2f[] poly)\n{\n    size_t i = 0;\n    size_t j = poly.length - 1;\n    bool inside = false;\n    float minDistance = float.max;\n\n    for (i = 0; i < poly.length; i++)\n    {\n        Vector2f a = poly[i];\n        Vector2f b = poly[j];\n\n        float lx = (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x;\n\n        if ((a.y > p.y) != (b.y > p.y))\n        {\n            if (p.x < lx)\n                inside = !inside;\n\n            float dist = sqrDistanceToLineSegment(a, b, p);\n            if (dist < minDistance)\n                minDistance = dist;\n        }\n\n        j = i;\n    }\n\n    float cd = 1.0f - clamp(sqrt(minDistance), 0.0f, 1.0f);\n    return max2(cast(float)inside, cd);\n}\n"
  },
  {
    "path": "dlib/image/color.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * RGBA color space\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.color;\r\n\r\nimport dlib.math.vector;\r\nimport dlib.math.utils;\r\n\r\n/// RGBA color channel\r\nenum Channel\r\n{\r\n    R = 0,\r\n    G = 1,\r\n    B = 2,\r\n    A = 3\r\n}\r\n\r\n/// RGBA 16-bit integer color representation (a vector of ushorts)\r\nalias Color4 = Vector!(ushort, 4);\r\n\r\n/// ditto\r\nalias ColorRGBA = Color4;\r\n\r\nColor4 invert(Color4 c)\r\n{\r\n    return Color4(\r\n        cast(ushort)(255 - c.r),\r\n        cast(ushort)(255 - c.g),\r\n        cast(ushort)(255 - c.b),\r\n        c.a);\r\n}\r\n\r\n/**\r\n * RGBA floating-point color representation,\r\n * encapsulates Vector4f\r\n */\r\nstruct Color4f\r\n{\r\n    Vector4f vec;\r\n    alias vec this;\r\n\r\n    this(Color4 c, uint bitDepth = 8)\r\n    {\r\n        float maxv = (2 ^^ bitDepth) - 1;\r\n        vec.r = c.r / maxv;\r\n        vec.g = c.g / maxv;\r\n        vec.b = c.b / maxv;\r\n        vec.a = c.a / maxv;\r\n    }\r\n\r\n    this(Color4f c)\r\n    {\r\n        vec = c.vec;\r\n    }\r\n\r\n    this(Vector4f v)\r\n    {\r\n        vec = v;\r\n    }\r\n\r\n    this(Vector3f v)\r\n    {\r\n        vec = Vector4f(v.x, v.y, v.z, 1.0f);\r\n    }\r\n\r\n    this(float cr, float cg, float cb, float ca = 1.0f)\r\n    {\r\n        vec = Vector4f(cr, cg, cb, ca);\r\n    }\r\n\r\n    static Color4f zero()\r\n    {\r\n        return Color4f(0.0f, 0.0f, 0.0f, 0.0f);\r\n    }\r\n\r\n    Color4f opAssign(Color4f c)\r\n    {\r\n        vec = c.vec;\r\n        return this;\r\n    }\r\n\r\n    Color4f opBinary(string op)(float x) if (op == \"+\")\r\n    {\r\n        return Color4f(this.vec + x);\r\n    }\r\n\r\n    Color4f opBinary(string op)(float x) if (op == \"-\")\r\n    {\r\n        return Color4f(this.vec - x);\r\n    }\r\n\r\n    Color4f opBinary(string op)(float x) if (op == \"*\")\r\n    {\r\n        return Color4f(this.vec * x);\r\n    }\r\n\r\n    Color4f opBinary(string op)(float x) if (op == \"/\")\r\n    {\r\n        return Color4f(this.vec / x);\r\n    }\r\n\r\n    Color4f opBinary(string op)(Vector4f v) if (op == \"+\")\r\n    {\r\n        return Color4f(this.vec + v);\r\n    }\r\n\r\n    Color4f opBinary(string op)(Vector4f v) if (op == \"-\")\r\n    {\r\n        return Color4f(this.vec - v);\r\n    }\r\n\r\n    Color4f opBinary(string op)(Vector4f v) if (op == \"*\")\r\n    {\r\n        return Color4f(this.vec * v);\r\n    }\r\n\r\n    Color4f opBinary(string op)(Vector4f v) if (op == \"/\")\r\n    {\r\n        return Color4f(this.vec / v);\r\n    }\r\n\r\n    Color4 convert(int bitDepth)\r\n    {\r\n        float maxv = (2 ^^ bitDepth) - 1;\r\n        return Color4(\r\n            cast(ushort)(r.clamp(0.0f, 1.0f) * maxv),\r\n            cast(ushort)(g.clamp(0.0f, 1.0f) * maxv),\r\n            cast(ushort)(b.clamp(0.0f, 1.0f) * maxv),\r\n            cast(ushort)(a.clamp(0.0f, 1.0f) * maxv)\r\n        );\r\n    }\r\n\r\n    int opCmp(ref const(Color4f) c) const\r\n    {\r\n        return cast(int)((luminance() - c.luminance()) * 100);\r\n    }\r\n\r\n    alias luminance = luminance709;\r\n\r\n    // ITU-R Rec. BT.709\r\n    float luminance709() const\r\n    {\r\n        return (\r\n            vec.arrayof[0] * 0.2126f +\r\n            vec.arrayof[1] * 0.7152f +\r\n            vec.arrayof[2] * 0.0722f\r\n        );\r\n    }\r\n\r\n    // ITU-R Rec. BT.601\r\n    float luminance601() const\r\n    {\r\n        return (\r\n            vec.arrayof[0] * 0.3f +\r\n            vec.arrayof[1] * 0.59f +\r\n            vec.arrayof[2] * 0.11f\r\n        );\r\n    }\r\n\r\n    @property Color4f inverse()\r\n    {\r\n        return Color4f(\r\n            1.0f - vec.r,\r\n            1.0f - vec.g,\r\n            1.0f - vec.b,\r\n            vec.a);\r\n    }\r\n\r\n    @property Color4f clamped(float minv, float maxv)\r\n    {\r\n        return Color4f(\r\n            vec.r.clamp(minv, maxv),\r\n            vec.g.clamp(minv, maxv),\r\n            vec.b.clamp(minv, maxv),\r\n            vec.a.clamp(minv, maxv)\r\n        );\r\n    }\r\n\r\n    /// Converts color from gamma space to linear space\r\n    Color4f toLinear(float gamma = 2.2f)\r\n    {\r\n        float lr = r ^^ gamma;\r\n        float lg = g ^^ gamma;\r\n        float lb = b ^^ gamma;\r\n        return Color4f(lr, lg, lb, a);\r\n    }\r\n\r\n    /// Converts color from linear space to gamma space\r\n    Color4f toGamma(float gamma = 2.2f)\r\n    {\r\n        float invGamma = 1.0f / gamma;\r\n        float lr = r ^^ invGamma;\r\n        float lg = g ^^ invGamma;\r\n        float lb = b ^^ invGamma;\r\n        return Color4f(lr, lg, lb, a);\r\n    }\r\n}\r\n\r\n/// ditto\r\nalias ColorRGBAf = Color4f;\r\n\r\n///\r\nunittest\r\n{\r\n    Color4f c1 = Color4f(0.5f, 0.5f, 0.5f, 1.0f);\r\n    assert(isConsiderZero(c1.luminance - 0.5f));\r\n    assert(isConsiderZero(c1.luminance601 - 0.5f));\r\n    \r\n    Color4f c2 = Color4f(1.0f, 0.0f, 0.0f, 1.0f);\r\n    assert(isAlmostZero(c2.inverse - Color4f(0.0f, 1.0f, 1.0f, 1.0f)));\r\n}\r\n\r\n/// Encode a normal vector to color\r\nColor4f packNormal(Vector3f n)\r\n{\r\n    return Color4f((n + 1.0f) * 0.5f);\r\n}\r\n\r\n/// 24-bit integer color unpacking\r\nColor4f color3(int hex)\r\n{\r\n    ubyte r = (hex >> 16) & 255;\r\n    ubyte g = (hex >> 8) & 255;\r\n    ubyte b = hex & 255;\r\n    return Color4f(\r\n        cast(float)r / 255.0f,\r\n        cast(float)g / 255.0f,\r\n        cast(float)b / 255.0f);\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(color3(0xff0000) == Color4f(1.0f, 0.0f, 0.0f, 1.0f));\r\n}\r\n\r\n/// 32-bit integer color unpacking\r\nColor4f color4(int hex)\r\n{\r\n    ubyte r = (hex >> 24) & 255;\r\n    ubyte g = (hex >> 16) & 255;\r\n    ubyte b = (hex >> 8) & 255;\r\n    ubyte a = hex & 255;\r\n    return Color4f(\r\n        cast(float)r / 255.0f,\r\n        cast(float)g / 255.0f,\r\n        cast(float)b / 255.0f,\r\n        cast(float)a / 255.0f);\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(color4(0xff000000) == Color4f(1.0f, 0.0f, 0.0f, 0.0f));\r\n}\r\n\r\n/// Blend two colors taking transparency into account\r\nColor4f alphaOver(Color4f c1, Color4f c2)\r\n{\r\n    Color4f c;\r\n    float a = c2.a + c1.a * (1.0f - c2.a);\r\n\r\n    if (a == 0.0f)\r\n        c = Color4f(0, 0, 0, 0);\r\n    else\r\n    {\r\n        c = (c2 * c2.a + c1 * c1.a * (1.0f - c2.a)) / a;\r\n        c.a = a;\r\n    }\r\n\r\n    return c;\r\n}\r\n\r\n/**\r\n * Is all elements almost zero\r\n */\r\nbool isAlmostZero(Color4f c)\r\n{\r\n    return (isConsiderZero(c.r) &&\r\n            isConsiderZero(c.g) &&\r\n            isConsiderZero(c.b) &&\r\n            isConsiderZero(c.a));\r\n}\r\n"
  },
  {
    "path": "dlib/image/filters/binarization.d",
    "content": "/*\r\nCopyright (c) 2018-2025 Oleg Baharev, Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\n\n/**\n * Image binarization\n *\n * Copyright: Oleg Baharev, Timur Gafarov 2018-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Oleg Baharev, Timur Gafarov\n */\nmodule dlib.image.filters.binarization;\n\nimport dlib.image.image;\nimport dlib.image.color;\nimport dlib.image.filters.histogram;\n\nint otsuThreshold(SuperImage img)\n{\n    auto histogram = createHistogram(img);\n\n    int sumOfLuminances;\n\n    foreach (x; 0..img.width)\n    foreach (y; 0..img.height)\n    {\n        sumOfLuminances += cast(int)(img[x, y].luminance * 255); \n    }\n\n    auto allPixelCount = cast(double)(img.width * img.height);\n    \n    int bestThreshold = 0;\n    int firstClassPixelCount = 0;\n    int firstClassLuminanceSum = 0;\n    \n    double bestSigma = 0.0;\n\n    for (int threshold = 0; threshold < 255; threshold++)\n    {\n        firstClassPixelCount += histogram[threshold];\n        firstClassLuminanceSum += threshold * histogram[threshold];\n    \n        double firstClassProbability = firstClassPixelCount / allPixelCount;\n        double secondClassProbability = 1.0 - firstClassProbability;\n\n        double firstClassMean = (firstClassPixelCount == 0) ? 0 : firstClassLuminanceSum / firstClassPixelCount;\n        double secondClassMean = (sumOfLuminances - firstClassLuminanceSum) / (allPixelCount - firstClassPixelCount);\n        \n        double meanDelta = firstClassMean - secondClassMean;\n        double sigma = firstClassProbability * secondClassProbability * meanDelta * meanDelta;\n\n        if (sigma > bestSigma)\n        {\n            bestSigma = sigma;\n            bestThreshold = threshold;\n        }\n    }\n\n    return bestThreshold;\n}\n\n/// Otsu binarization\nauto otsuBinarization(SuperImage img)\n{\n    SuperImage res = img.createSameFormat(img.width, img.height);\n    auto threshold = otsuThreshold(img);\n\n    foreach (x; 0..img.width)\n    foreach (y; 0..img.height)\n    {\n        auto luminance = cast(int)(img[x,y].luminance * 255);\n\n        if (luminance > threshold)\n            res[x, y] = Color4f(1.0f, 1.0f, 1.0f, 1.0f);\n        else\n            res[x, y] = Color4f(0.0f, 0.0f, 0.0f, 1.0f);\n    }\n\n    return res; \n}\n"
  },
  {
    "path": "dlib/image/filters/boxblur.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Box blur\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.filters.boxblur;\r\n\r\nimport dlib.image.color;\r\nimport dlib.image.image;\r\n\r\n/// Blur an image\r\nSuperImage boxBlur(SuperImage img, SuperImage outp, int radius)\r\n{\r\n    SuperImage res;\r\n    if (outp)\r\n        res = outp;\r\n    else\r\n        res = img.dup;\r\n\r\n    immutable int boxSide = radius * 2 + 1;\r\n    immutable int boxSide2 = boxSide * boxSide;\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        float alpha = Color4f(img[x, y]).a;\r\n\r\n        Color4f total = Color4f(0, 0, 0);\r\n\r\n        foreach(ky; 0..boxSide)\r\n        foreach(kx; 0..boxSide)\r\n        {\r\n            int iy = y + (ky - radius);\r\n            int ix = x + (kx - radius);\r\n\r\n            total += img[ix, iy];\r\n        }\r\n\r\n        total /= boxSide2;\r\n        total.a = alpha;\r\n\r\n        res[x,y] = total;\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/// ditto\r\nSuperImage boxBlur(SuperImage img, int radius)\r\n{\r\n    return boxBlur(img, null, radius);\r\n}\r\n"
  },
  {
    "path": "dlib/image/filters/chromakey.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Filters that remove background from images \r\n *\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.filters.chromakey;\r\n\r\nimport dlib.math.utils;\r\nimport dlib.image.image;\r\nimport dlib.image.color;\r\nimport dlib.image.hsv;\r\nimport dlib.math.vector;\r\nimport dlib.math.utils;\r\n\r\n/// Euclidean distance chroma key\r\nSuperImage chromaKeyEuclidean(\r\n    SuperImage img,\r\n    SuperImage outp,\r\n    Color4f keyColor,\r\n    float minDist,\r\n    float maxDist)\r\n{\r\n    SuperImage res;\r\n    if (outp)\r\n        res = outp;\r\n    else\r\n        res = img.dup;\r\n\r\n    foreach(y; img.col)\r\n    foreach(x; img.row)\r\n    {\r\n        Color4f col = img[x, y];\r\n\r\n        Color4f delta = col - keyColor;\r\n        float distSqr = dot(delta, delta);\r\n        col.a = clamp(\r\n            (distSqr - minDist) / (maxDist - minDist),\r\n            0.0f, 1.0f);\r\n        res[x, y] = col;\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/// ditto\r\nSuperImage chromaKeyEuclidean(\r\n    SuperImage img,\r\n    Color4f keyColor,\r\n    float minDist,\r\n    float maxDist)\r\n{\r\n    return chromaKeyEuclidean(img, null, keyColor, minDist, maxDist);\r\n}\r\n\r\n/// HSV selective scale chroma key\r\nSuperImage chromaKey(\r\n    SuperImage img,\r\n    SuperImage outp,\r\n    float hue,\r\n    float hueToleranceMin = -20.0f,\r\n    float hueToleranceMax = 20.0f,\r\n    float satThres = 0.2f,\r\n    float valThres = 0.3f)\r\n{\r\n    SuperImage res;\r\n    if (outp)\r\n        res = outp;\r\n    else\r\n        res = img.dup;\r\n\r\n    foreach(x; 0..img.width)\r\n    foreach(y; 0..img.height)\r\n    {\r\n        Color4f col = res[x, y];\r\n        ColorHSVAf hsva = ColorHSVAf(col);\r\n\r\n        hsva.selectiveScale(\r\n            hue,\r\n            HSVAChannel.A,\r\n            0.0f,\r\n            false,\r\n            hueToleranceMin,\r\n            hueToleranceMax,\r\n            satThres,\r\n            valThres);\r\n\r\n        res[x, y] = hsva.rgba;\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/// ditto\r\nSuperImage chromaKey(\r\n    SuperImage img,\r\n    float hue,\r\n    float hueToleranceMin = -20.0f,\r\n    float hueToleranceMax = 20.0f,\r\n    float satThres = 0.2f,\r\n    float valThres = 0.3f)\r\n{\r\n    return chromaKey(img, null, hue, hueToleranceMin, hueToleranceMax, satThres, valThres);\r\n}\r\n\r\n/// Turns image into b&w where only one color left, using HSV selective scale\r\nSuperImage colorPass(\r\n    SuperImage img,\r\n    SuperImage outp,\r\n    float hue,\r\n    float hueToleranceMin = -20.0f,\r\n    float hueToleranceMax = 20.0f,\r\n    float satThres = 0.2f,\r\n    float valThres = 0.3f)\r\nin\r\n{\r\n    assert (img.data.length);\r\n}\r\ndo\r\n{\r\n    SuperImage res;\r\n    if (outp)\r\n        res = outp;\r\n    else\r\n        res = img.dup;\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        Color4f col = res[x, y];\r\n        ColorHSVAf hsva = ColorHSVAf(col);\r\n        hsva.selectiveScale(\r\n            hue,\r\n            HSVAChannel.S,\r\n            0.0f,\r\n            true,\r\n            hueToleranceMin,\r\n            hueToleranceMax,\r\n            satThres,\r\n            valThres);\r\n\r\n        res[x, y] = hsva.rgba;\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/// ditto\r\nSuperImage colorPass(\r\n    SuperImage img,\r\n    float hue,\r\n    float hueToleranceMin = -20.0f,\r\n    float hueToleranceMax = 20.0f,\r\n    float satThres = 0.2f,\r\n    float valThres = 0.3f)\r\n{\r\n    return colorPass(img, null, hue, hueToleranceMin, hueToleranceMax, satThres, valThres);\r\n}\r\n\r\nprivate:\r\n\r\nvoid selectiveScale(ref ColorHSVAf col,\r\n                    float hue,\r\n                    HSVAChannel chan,\r\n                    float scale,\r\n                    bool inverse,\r\n                    float hueToleranceMin = -20.0f,\r\n                    float hueToleranceMax = 20.0f,\r\n                    float satThres = 0.2f,\r\n                    float valThres = 0.3f)\r\n{\r\n    while (hue >= 360.0f)\r\n        hue -= 360.0f;\r\n    while (hue < 0.0f)\r\n        hue += 360.0f;\r\n\r\n    if (col.hueInRange(hue, hueToleranceMin, hueToleranceMax)\r\n        && col.s > satThres\r\n        && col.v > valThres)\r\n    {\r\n        if (!inverse)\r\n            col.arrayof[chan] *= scale;\r\n    }\r\n    else\r\n    {\r\n        if (inverse)\r\n            col.arrayof[chan] *= scale;\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/image/filters/contrast.d",
    "content": "/*\nCopyright (c) 2011-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Adjust contrast\n *\n * Copyright: Timur Gafarov 2011-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.image.filters.contrast;\n\nimport dlib.image.image;\nimport dlib.image.color;\n\n/// Contrast method\nenum ContrastMethod\n{\n    AverageGray,\n    AverageImage,\n}\n\n/// Adjust contrast\nSuperImage contrast(SuperImage img, SuperImage outp, float k, ContrastMethod method = ContrastMethod.AverageGray)\n{\n    SuperImage res;\n    if (outp)\n        res = outp;\n    else\n        res = img.dup;\n\n    Color4f aver = Color4f(0.0f, 0.0f, 0.0f);\n\n    if (method == ContrastMethod.AverageGray)\n    {\n        aver = Color4f(0.5f, 0.5f, 0.5f);\n    }\n    else if (method == ContrastMethod.AverageImage)\n    {\n        foreach(y; 0..res.height)\n        foreach(x; 0..res.width)\n        {\n            aver += img[x, y];\n        }\n\n        aver /= (res.height * res.width);\n    }\n\n    foreach(y; 0..res.height)\n    foreach(x; 0..res.width)\n    {\n        auto col = img[x, y];\n        col = ((col - aver) * k + aver);\n        res[x, y] = col;\n    }\n\n    return res;\n}\n\n/// ditto\nSuperImage contrast(SuperImage a, float k, ContrastMethod method = ContrastMethod.AverageGray)\n{\n    return contrast(a, null, k, method);\n}\n"
  },
  {
    "path": "dlib/image/filters/convolution.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Image convolution\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.filters.convolution;\r\n\r\nimport std.algorithm;\r\nimport dlib.image.image;\r\nimport dlib.image.color;\r\n\r\n/// Convolve an image with a kernel\r\nSuperImage convolve(SuperImage img,\r\n                    SuperImage outp,\r\n                    float[] kernel,\r\n                    uint kw = 3,\r\n                    uint kh = 3,\r\n                    float divisor = 1.0f,\r\n                    float offset = 0.5f,\r\n                    bool normalize = true,\r\n                    bool useAlpha = true)\r\nin\r\n{\r\n    assert(img.data.length);\r\n    assert(kernel.length == kw * kh);\r\n}\r\ndo\r\n{\r\n    SuperImage res;\r\n    if (outp)\r\n        res = outp;\r\n    else\r\n        res = img.dup;\r\n\r\n    float kernelSum = reduce!((a,b) => a + b)(kernel);\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        float alpha = Color4f(img[x, y]).a;\r\n\r\n        Color4f csum = Color4f(0, 0, 0);\r\n\r\n        foreach(ky; 0..kh)\r\n        foreach(kx; 0..kw)\r\n        {\r\n            int iy = y + (ky - kh/2);\r\n            int ix = x + (kx - kw/2);\r\n\r\n            // Extend\r\n            if (ix < 0) ix = 0;\r\n            if (ix >= img.width) ix = img.width - 1;\r\n            if (iy < 0) iy = 0;\r\n            if (iy >= img.height) iy = img.height - 1;\r\n\r\n            // TODO:\r\n            // Wrap\r\n\r\n            auto pix = Color4f(img[ix, iy]);\r\n            auto k = kernel[kx + ky * kw];\r\n\r\n            csum += pix * k;\r\n        }\r\n\r\n        if (normalize)\r\n        {\r\n            offset = 0.0f;\r\n            divisor = kernelSum;\r\n\r\n            if (divisor == 0.0f)\r\n            {\r\n                divisor = 1.0f;\r\n                offset = 0.5f;\r\n            }\r\n\r\n            if (divisor < 0.0f)\r\n                offset = 1.0f;\r\n        }\r\n\r\n        csum = csum / divisor + offset;\r\n\r\n        if (!useAlpha)\r\n            csum.a = alpha;\r\n\r\n        res[x,y] = csum;\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/// ditto\r\nSuperImage convolve(SuperImage img,\r\n                    float[] kernel,\r\n                    uint kw = 3,\r\n                    uint kh = 3,\r\n                    float divisor = 1.0f,\r\n                    float offset = 0.5f,\r\n                    bool normalize = true,\r\n                    bool useAlpha = true)\r\n{\r\n    return convolve(img, null, kernel, kw, kh, divisor, offset, normalize, useAlpha);\r\n}\r\n\r\n/// Various built-in convolution kernels\r\nstruct Kernel\r\n{\r\n    enum float[]\r\n\r\n    Identity =\r\n    [\r\n        0, 0, 0,\r\n        0, 1, 0,\r\n        0, 0, 0\r\n    ],\r\n\r\n    BoxBlur =\r\n    [\r\n        1, 1, 1,\r\n        1, 1, 1,\r\n        1, 1, 1\r\n    ],\r\n\r\n    GaussianBlur =\r\n    [\r\n        1, 2, 1,\r\n        2, 4, 2,\r\n        1, 2, 1\r\n    ],\r\n\r\n    Sharpen =\r\n    [\r\n        -1, -1, -1,\r\n        -1, 11, -1,\r\n        -1, -1, -1\r\n    ],\r\n\r\n    Emboss =\r\n    [\r\n       -1, -1,  0,\r\n       -1,  0,  1,\r\n        0,  1,  1,\r\n    ],\r\n\r\n    EdgeEmboss =\r\n    [\r\n        -1.0f, -0.5f, -0.0f,\r\n        -0.5f,  1.0f,  0.5f,\r\n        -0.0f,  0.5f,  1.0f\r\n    ],\r\n\r\n    EdgeDetect =\r\n    [\r\n        -1, -1, -1,\r\n        -1,  8, -1,\r\n        -1, -1, -1,\r\n    ],\r\n\r\n    Laplace =\r\n    [\r\n        0,  1,  0,\r\n        1, -4,  1,\r\n        0,  1,  0,\r\n    ];\r\n}\r\n"
  },
  {
    "path": "dlib/image/filters/desaturate.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Turn image to black and white\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.filters.desaturate;\r\n\r\nimport dlib.image.image;\r\nimport dlib.image.color;\r\n\r\n/// Default desaturate filter\r\nalias desaturate = desaturate709;\r\n\r\n/// ITU-R recommendation BT.709\r\nSuperImage desaturate709(SuperImage img, SuperImage outp = null)\r\n{\r\n    SuperImage res;\r\n    if (outp)\r\n        res = outp;\r\n    else\r\n        res = img.dup;\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        auto color = img[x, y];\r\n        float l = color.luminance709;\r\n        res[x, y] = Color4f(l, l, l, color.a);\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/// ITU-R recommendation BT.601\r\nSuperImage desaturate601(SuperImage img, SuperImage outp = null)\r\n{\r\n    SuperImage res;\r\n    if (outp)\r\n        res = outp;\r\n    else\r\n        res = img.dup;\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        auto color = img[x, y];\r\n        float l = color.luminance601;\r\n        res[x, y] = Color4f(l, l, l, color.a);\r\n    }\r\n\r\n    return res;\r\n}\r\n"
  },
  {
    "path": "dlib/image/filters/edgedetect.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov, Oleg Baharev\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Detect edges on an image\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.filters.edgedetect;\r\n\r\nimport std.math;\r\nimport dlib.math.vector;\r\nimport dlib.image.image;\r\nimport dlib.image.color;\r\nimport dlib.image.arithmetics;\r\nimport dlib.image.filters.contrast;\r\nimport dlib.image.filters.boxblur;\r\nimport dlib.image.filters.morphology;\r\nimport dlib.image.filters.convolution;\r\n\r\n/// Difference of Gaussians\r\nSuperImage edgeDetectDoG(SuperImage src, SuperImage outp, int radius1, int radius2, float amount, bool inv = true)\r\n{\r\n    if (outp is null)\r\n        outp = src.dup;\r\n\r\n    auto blurred1 = boxBlur(src, outp, radius1);\r\n    SuperImage outp2 = outp.dup;\r\n    auto blurred2 = boxBlur(src, outp2, radius2);\r\n\r\n    auto mask = subtract(blurred1, blurred2, outp, 1.0f);\r\n    outp2.free();\r\n    auto highcon = contrast(mask, mask, amount, ContrastMethod.AverageImage);\r\n\r\n    if (inv)\r\n        return invert(highcon, highcon);\r\n    else\r\n        return highcon;\r\n}\r\n\r\n/// ditto\r\nSuperImage edgeDetectDoG(SuperImage src, int radius1, int radius2, float amount, bool inv = true)\r\n{\r\n    return edgeDetectDoG(src, null, radius1, radius2, amount, inv);\r\n}\r\n\r\n/// Morphologic edge detection\r\nSuperImage edgeDetectGradient(SuperImage src, SuperImage outp)\r\n{\r\n    if (outp is null)\r\n        outp = src.dup;\r\n\r\n    return gradient(src, outp);\r\n}\r\n\r\n/// ditto\r\nSuperImage edgeDetectGradient(SuperImage src)\r\n{\r\n    return edgeDetectGradient(src, null);\r\n}\r\n\r\n/// Laplace edge detection\r\nSuperImage edgeDetectLaplace(SuperImage src, SuperImage outp)\r\n{\r\n    if (outp is null)\r\n        outp = src.dup;\r\n\r\n    return convolve(src, outp, Kernel.Laplace, 3, 3, 1.0f, 0.0f, false);\r\n}\r\n\r\n/// ditto\r\nSuperImage edgeDetectLaplace(SuperImage src)\r\n{\r\n    return edgeDetectLaplace(src, null);\r\n}\r\n\r\n/// Sobel edge detection\r\nSuperImage edgeDetectSobel(SuperImage src, SuperImage outp, float normFactor = 1.0f / 8.0f)\r\n{\r\n    if (outp is null)\r\n        outp = src.dup;\r\n    \r\n    enum float[3][3] sobelHorizontal = [\r\n        [-1,  0,  1],\r\n        [-2,  0,  2],\r\n        [-1,  0,  1],\r\n    ];\r\n    \r\n    enum float[3][3] sobelVertical = [\r\n        [-1, -2, -1],\r\n        [ 0,  0,  0],\r\n        [ 1,  2,  1],\r\n    ];\r\n    \r\n    foreach(window, x, y; src.windows(3, 3))\r\n    {\r\n        Color4f hor = Color4f(0, 0, 0);\r\n        Color4f ver = Color4f(0, 0, 0);\r\n        foreach(ref Color4f pixel, x, y; window)\r\n        {\r\n            hor += pixel * sobelHorizontal[y][x];\r\n            ver += pixel * sobelVertical[y][x];\r\n        }\r\n        \r\n        float magnitude = sqrt(hor.xyz.lengthsqr + ver.xyz.lengthsqr) * normFactor;\r\n        Color4f res = Color4f(magnitude, magnitude, magnitude, 1.0f);\r\n        res.a = 1.0f;\r\n        outp[x, y] = res;\r\n    }\r\n    \r\n    return outp;\r\n}\r\n\r\n/// ditto\r\nSuperImage edgeDetectSobel(SuperImage src, float normFactor = 1.0f / 8.0f)\r\n{\r\n    return edgeDetectSobel(src, null, normFactor);\r\n}\r\n"
  },
  {
    "path": "dlib/image/filters/histogram.d",
    "content": "/*\r\nCopyright (c) 2018-2025 Oleg Baharev, Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\n\n/**\n * Generate histogram of an image\n *\n * Copyright: Oleg Baharev, Timur Gafarov 2018-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Oleg Baharev, Timur Gafarov\n */\nmodule dlib.image.filters.histogram;\n\nimport dlib.image.image;\nimport dlib.image.color;\n\n/// Obtain histogram\nint[256] createHistogram(SuperImage img)\n{\n    int[256] histogram;\n\n    foreach (x; 0..img.width)\n    foreach (y; 0..img.height)\n    {\n        int luma = cast(int)(img[x,y].luminance * 255);\n        histogram[luma] += 1; \n    }\n\n    return histogram;\n}\n\n/// Generate histogram image\nSuperImage histogramImage(SuperImage img, Color4f background, Color4f diagram)\n{\n    SuperImage res = img.createSameFormat(256, 256);\n    int[256] h = createHistogram(img);\n\n    int vmax = 0;\n    foreach(v; h)\n    {\n        if (v > vmax)\n            vmax = v;\n    }\n\n    foreach(ref v; h)\n    {\n        v = cast(int)(cast(float)v / cast(float)vmax * 255.0f);\n    }\n\n    foreach (x; 0..res.width)\n    foreach (y; 0..res.height)\n    {\n        int v = h[x];\n        if (y < 255 - v)\n            res[x, y] = background;\n        else\n            res[x, y] = diagram;\n    }\n    return res;\n}\n"
  },
  {
    "path": "dlib/image/filters/lens.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Lens distortion filter\r\n *\r\n * Copyright: Timur Gafarov 2014-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.filters.lens;\r\n\r\nimport std.math;\r\nimport dlib.image.image;\r\n\r\n/// Apply lens distortion filter\r\nSuperImage lensDistortion(\r\n    SuperImage img,\r\n    SuperImage outp,\r\n    float strength,\r\n    float zoom,\r\n    bool interpolation = true)\r\n{\r\n    SuperImage res;\r\n    if (outp)\r\n        res = outp;\r\n    else\r\n        res = img.dup;\r\n\r\n    float halfWidth = cast(float)img.width / 2.0f;\r\n    float halfHeight = cast(float)img.height / 2.0f;\r\n\r\n    float correctionRadius = sqrt(cast(float)(img.width ^^ 2 + img.height ^^ 2)) / strength;\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        float newX = x - halfWidth;\r\n        float newY = y - halfHeight;\r\n\r\n        float distance = sqrt(newX ^^ 2 + newY ^^ 2);\r\n        float r = distance / correctionRadius;\r\n\r\n        float theta;\r\n        if (r == 0)\r\n            theta = 1;\r\n        else\r\n            theta = atan(r) / r;\r\n\r\n        float sourceX = (halfWidth + theta * newX * zoom);\r\n        float sourceY = (halfHeight + theta * newY * zoom);\r\n\r\n        if (interpolation)\r\n            res[x, y] = img.bilinearPixel(sourceX, sourceY);\r\n        else\r\n            res[x, y] = img[cast(int)sourceX, cast(int)sourceY];\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/// ditto\r\nSuperImage lensDistortion(\r\n    SuperImage img,\r\n    float strength,\r\n    float zoom,\r\n    bool interpolation = true)\r\n{\r\n    return lensDistortion(img, null, strength, zoom, interpolation);\r\n}\r\n"
  },
  {
    "path": "dlib/image/filters/median.d",
    "content": "/*\nCopyright (c) 2022-2025 Oleg Baharev, Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Median filter.\n *\n * Copyright: Oleg Baharev, Timur Gafarov 2022-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Oleg Baharev, Timur Gafarov\n */\nmodule dlib.image.filters.median;\n\nimport std.algorithm: sort;\nimport dlib.core.memory;\nimport dlib.image.image;\nimport dlib.image.color;\n\n/// Median filter\nauto medianFilter(SuperImage img, SuperImage outp, uint windowWidth, uint windowHeight)\n{\n    SuperImage res;\n    if (outp)\n        res = outp;\n    else\n        res = img.dup;\n    \n    struct ColorAndLuma\n    {\n        Color4f color;\n        float luminance;\n    }\n    \n    ColorAndLuma[] window = New!(ColorAndLuma[])(windowWidth * windowHeight);\n    \n    uint halfWindowWidth = windowWidth / 2;\n    uint halfWindowHeight = windowHeight / 2;\n    \n    foreach(y; 0..img.height)\n    foreach(x; 0..img.width)\n    {\n        foreach(wy; 0..windowHeight)\n        foreach(wx; 0..windowWidth)\n        {\n            int sampleX = x - halfWindowWidth + wx;\n            int sampleY = y - halfWindowHeight + wy;\n            if (sampleX < 0) sampleX = 0;\n            else if (sampleX >= img.width) sampleX = img.width - 1;\n            if (sampleY < 0) sampleY = 0;\n            else if (sampleY >= img.height) sampleY = img.height - 1;\n            \n            auto sample = img[sampleX, sampleY];\n            \n            auto windowSample = &window[wy * windowWidth + wx];\n            windowSample.color = sample;\n            windowSample.luminance = sample.luminance;\n        }\n        \n        sort!(\"a.luminance > b.luminance\")(window);\n        res[x, y] = window[$ / 2].color;\n    }\n    \n    Delete(window);\n    \n    return res;\n}\n\n/// ditto\nSuperImage medianFilter(SuperImage img, uint windowWidth, uint windowHeight)\n{\n    return medianFilter(img, null, windowWidth, windowHeight);\n}\n"
  },
  {
    "path": "dlib/image/filters/morphology.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Morphologic filters\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.filters.morphology;\r\n\r\nimport dlib.image.color;\r\nimport dlib.image.image;\r\nimport dlib.image.arithmetics;\r\n\r\n/// Morphologic operation\r\nenum MorphOperation\r\n{\r\n    Dilate,\r\n    Erode\r\n}\r\n\r\n/// Apply morphologic operation\r\nSuperImage morphOp(MorphOperation op)(SuperImage img, SuperImage outp)\r\nin\r\n{\r\n    assert(img.data.length);\r\n}\r\ndo\r\n{\r\n    // TODO:\r\n    // add support for other structuring elements\r\n    // other than box (disk, diamond, etc)\r\n    \r\n    SuperImage res;\r\n    if (outp)\r\n        res = outp;\r\n    else\r\n        res = img.dup;\r\n\r\n    uint kw = 3, kh = 3;\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        static if (op == MorphOperation.Dilate)\r\n        {\r\n            Color4f resc = Color4f(0, 0, 0, 1);\r\n        }\r\n        static if (op == MorphOperation.Erode)\r\n        {\r\n            Color4f resc = img[x, y];\r\n        }\r\n\r\n        foreach(ky; 0..kh)\r\n        foreach(kx; 0..kw)\r\n        {\r\n            int iy = y + (ky - kh/2);\r\n            int ix = x + (kx - kw/2);\r\n\r\n            // Extend\r\n            if (ix < 0) ix = 0;\r\n            if (ix >= img.width) ix = img.width - 1;\r\n            if (iy < 0) iy = 0;\r\n            if (iy >= img.height) iy = img.height - 1;\r\n\r\n            // TODO:\r\n            // Wrap\r\n\r\n            auto pix = img[ix, iy];\r\n\r\n            static if (op == MorphOperation.Dilate)\r\n            {\r\n                if (pix > resc)\r\n                    resc = pix;\r\n            }\r\n            static if (op == MorphOperation.Erode)\r\n            {\r\n                if (pix < resc)\r\n                    resc = pix;\r\n            }\r\n        }\r\n\r\n        res[x, y] = resc;\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/// Ditto\r\nSuperImage morph(MorphOperation op) (SuperImage img)\r\n{\r\n    return morphOp!(op)(img, null);\r\n}\r\n\r\n/// Dilate\r\nalias dilate = morph!(MorphOperation.Dilate);\r\n/// Erode\r\nalias erode = morph!(MorphOperation.Erode);\r\n\r\n/// Morphologic open\r\nSuperImage open(SuperImage img)\r\n{\r\n    return dilate(erode(img));\r\n}\r\n\r\n/// ditto\r\nSuperImage open(SuperImage img, SuperImage outp)\r\n{\r\n    if (outp is null)\r\n        outp = img.dup;\r\n    auto outp2 = outp.dup;\r\n\r\n    auto e = morphOp!(MorphOperation.Erode)(img, outp2);\r\n    auto d = morphOp!(MorphOperation.Dilate)(outp2, outp);\r\n    outp2.free();\r\n    return d;\r\n}\r\n\r\n/// Morphologic close\r\nSuperImage close(SuperImage img)\r\n{\r\n    return erode(dilate(img));\r\n}\r\n\r\n/// ditto\r\nSuperImage close(SuperImage img, SuperImage outp)\r\n{\r\n    if (outp is null)\r\n        outp = img.dup;\r\n    auto outp2 = outp.dup;\r\n\r\n    auto d = morphOp!(MorphOperation.Dilate)(img, outp2);\r\n    auto e = morphOp!(MorphOperation.Erode)(outp2, outp);\r\n    outp2.free();\r\n    return e;\r\n}\r\n\r\n/// Morphologic gradient\r\nSuperImage gradient(SuperImage img)\r\n{\r\n    return subtract(dilate(img), erode(img));\r\n}\r\n\r\n/// ditto\r\nSuperImage gradient(SuperImage img, SuperImage outp)\r\n{\r\n    if (outp is null)\r\n        outp = img.dup;\r\n    auto outp2 = outp.dup;\r\n\r\n    auto d = morphOp!(MorphOperation.Dilate)(img, outp2);\r\n    auto e = morphOp!(MorphOperation.Erode)(img, outp);\r\n    auto s = subtract(d, e, outp);\r\n    outp2.free();\r\n    return s;\r\n}\r\n\r\n/// White top-hat transform\r\nSuperImage topHatWhite(SuperImage img)\r\n{\r\n    return subtract(img, open(img));\r\n}\r\n\r\n/// ditto\r\nSuperImage topHatWhite(SuperImage img, SuperImage outp)\r\n{\r\n    if (outp is null)\r\n        outp = img.dup;\r\n    auto o = open(img, outp);\r\n    auto s = subtract(img, o, outp);\r\n    return s;\r\n}\r\n\r\n/// Black top-hat transform\r\nSuperImage topHatBlack(SuperImage img)\r\n{\r\n    return subtract(img, close(img));\r\n}\r\n\r\n/// ditto\r\nSuperImage topHatBlack(SuperImage img, SuperImage outp)\r\n{\r\n    if (outp is null)\r\n        outp = img.dup;\r\n    auto o = close(img, outp);\r\n    auto s = subtract(img, o, outp);\r\n    return s;\r\n}\r\n"
  },
  {
    "path": "dlib/image/filters/normalmap.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Normal map generation\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.filters.normalmap;\r\n\r\nimport dlib.image.image;\r\nimport dlib.image.color;\r\nimport dlib.math.vector;\r\n\r\n/// Generate normal map from height map using Sobel operator\r\nSuperImage heightToNormal(\r\n    SuperImage img,\r\n    SuperImage outp,\r\n    Channel channel = Channel.R,\r\n    float strength = 2.0f)\r\nin\r\n{\r\n    assert (img.data.length);\r\n}\r\ndo\r\n{\r\n    // TODO: optionally transfer height data to alpha channel\r\n    SuperImage res;\r\n    if (outp)\r\n        res = outp;\r\n    else\r\n        res = img.dup;\r\n\r\n    if (img.channels == 1)\r\n        channel = Channel.R;\r\n\r\n    float[8] sobelTaps;\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        sobelTaps[0] = img[x-1, y-1][channel];\r\n        sobelTaps[1] = img[x,   y-1][channel];\r\n        sobelTaps[2] = img[x+1, y-1][channel];\r\n        sobelTaps[3] = img[x-1, y+1][channel];\r\n        sobelTaps[4] = img[x,   y+1][channel];\r\n        sobelTaps[5] = img[x+1, y+1][channel];\r\n        sobelTaps[6] = img[x-1, y  ][channel];\r\n        sobelTaps[7] = img[x+1, y  ][channel];\r\n\r\n        float dx, dy;\r\n\r\n        // Do y sobel filter\r\n        dy  = sobelTaps[0] * +1.0f;\r\n        dy += sobelTaps[1] * +2.0f;\r\n        dy += sobelTaps[2] * +1.0f;\r\n        dy += sobelTaps[3] * -1.0f;\r\n        dy += sobelTaps[4] * -2.0f;\r\n        dy += sobelTaps[5] * -1.0f;\r\n\r\n        // Do x sobel filter\r\n        dx  = sobelTaps[0] * -1.0f;\r\n        dx += sobelTaps[6] * -2.0f;\r\n        dx += sobelTaps[3] * -1.0f;\r\n        dx += sobelTaps[2] * +1.0f;\r\n        dx += sobelTaps[7] * +2.0f;\r\n        dx += sobelTaps[5] * +1.0f;\r\n\r\n        // pack normal into floating-point RGBA\r\n        Vector3f normal = Vector3f(-dx, -dy, 1.0f / strength);\r\n        Color4f col = packNormal(normal);\r\n        col.a = 1.0f;\r\n\r\n        // write result\r\n        res[x, y] = col;\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/// ditto\r\nSuperImage heightToNormal(\r\n    SuperImage img,\r\n    Channel channel = Channel.R,\r\n    float strength = 2.0f)\r\n{\r\n    return heightToNormal(img, null, channel, strength);\r\n}\r\n"
  },
  {
    "path": "dlib/image/filters/package.d",
    "content": "/*\nCopyright (c) 2020-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Image filtering\n *\n * Copyright: Timur Gafarov 2020-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.image.filters;\n\npublic\n{\n    import dlib.image.filters.boxblur;\n    import dlib.image.filters.chromakey;\n    import dlib.image.filters.convolution;\n    import dlib.image.filters.desaturate;\n    import dlib.image.filters.edgedetect;\n    import dlib.image.filters.lens;\n    import dlib.image.filters.median;\n    import dlib.image.filters.morphology;\n    import dlib.image.filters.normalmap;\n    import dlib.image.filters.sharpen;\n    import dlib.image.filters.contrast;\n    import dlib.image.filters.histogram;\n    import dlib.image.filters.binarization;\n}\n"
  },
  {
    "path": "dlib/image/filters/sharpen.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Image sharpening\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.filters.sharpen;\r\n\r\nimport dlib.image.image;\r\nimport dlib.image.color;\r\nimport dlib.image.arithmetics;\r\nimport dlib.image.filters.contrast;\r\nimport dlib.image.filters.boxblur;\r\n\r\n/// Sharpen an image\r\nSuperImage sharpen(SuperImage src, SuperImage outp, int radius, float amount)\r\n{\r\n    if (outp is null)\r\n        outp = src.dup;\r\n\r\n    auto blurred = boxBlur(src, outp, radius);\r\n    auto mask = subtract(src, blurred, outp, 1.0f);\r\n    auto highcon = contrast(mask, outp, amount, ContrastMethod.AverageImage);\r\n    return add(src, highcon, outp, 0.25f);\r\n}\r\n\r\n/// ditto\r\nSuperImage sharpen(SuperImage src, int radius, float amount)\r\n{\r\n    return sharpen(src, null, radius, amount);\r\n}\r\n"
  },
  {
    "path": "dlib/image/fthread.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Threaded image filtering\r\n *\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.fthread;\r\n\nimport dlib.core.memory;\nimport dlib.core.thread;\r\nimport dlib.image.image;\r\n\r\n/**\r\n * An object that applies a filter function to an image in a separate thread\r\n */\r\nclass FilteringThread\r\n{\n    Thread thread;\r\n    protected SuperImage image;\r\n    protected SuperImage output;\r\n\r\n    this(SuperImage img)\r\n    {\n        thread = New!Thread(&threadFunc);\r\n        image = img;\n        output = img;\r\n    }\n\n    ~this()\n    {\n        Delete(thread);\n    }\n\n    void threadFunc()\n    {\n        run();\n    }\r\n\r\n    SuperImage filtered()\r\n    {\r\n        thread.start();\r\n        while(thread.isRunning)\r\n            onRunning();\r\n        onFinished();\r\n        return output;\r\n    }\r\n\r\n    /// Called in a second thread. Override it\r\n    void run() {}\r\n    \r\n    /// Called in main thread in a loop while second thread is running. Override it\r\n    void onRunning() {}\r\n    \r\n    /// Called in main thread once when second thread finishes. Override it\r\n    void onFinished() {}\r\n}\r\n"
  },
  {
    "path": "dlib/image/hdri.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * High dynamic range images\r\n *\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.hdri;\r\n\r\nimport core.stdc.string;\r\nimport std.math;\nimport dlib.core.memory;\r\nimport dlib.image.image;\r\nimport dlib.image.color;\nimport dlib.math.vector;\nimport dlib.math.utils;\r\n\r\n/// Linear floating-point pixel formats\nenum FloatPixelFormat: uint\r\n{\r\n    RGBAF32 = 8\n    //TODO:\n    //RGBAF64 = 9\n    //RGBAF16 = 10\r\n}\r\n\r\n/**\r\n * HDR image interface\r\n */\r\nabstract class SuperHDRImage: SuperImage\r\n{\r\n    override @property uint pixelFormat()\r\n    {\r\n        return FloatPixelFormat.RGBAF32;\r\n    }\r\n}\r\n\r\n/**\r\n * Extension of standard Image that is based on FloatPixelFormat.RGBAF32\r\n */\r\nclass HDRImage: SuperHDRImage\r\n{\r\n    public:\r\n\r\n    @property uint width()\r\n    {\r\n        return _width;\r\n    }\r\n\r\n    @property uint height()\r\n    {\r\n        return _height;\r\n    }\r\n\r\n    @property uint bitDepth()\r\n    {\r\n        return _bitDepth;\r\n    }\r\n\r\n    @property uint channels()\r\n    {\r\n        return _channels;\r\n    }\r\n\r\n    @property uint pixelSize()\r\n    {\r\n        return _pixelSize;\r\n    }\r\n\r\n    @property ubyte[] data()\r\n    {\r\n        return _data;\r\n    }\r\n\r\n    @property SuperImage dup()\r\n    {\r\n        auto res = new HDRImage(_width, _height);\r\n        res.data[] = data[];\r\n        return res;\r\n    }\r\n\r\n    SuperImage createSameFormat(uint w, uint h)\r\n    {\r\n        return new HDRImage(w, h);\r\n    }\r\n\r\n    this(uint w, uint h)\r\n    {\r\n        _width = w;\r\n        _height = h;\r\n        _bitDepth = 32;\r\n        _channels = 4;\r\n        _pixelSize = (_bitDepth / 8) * _channels;\r\n        allocateData();\r\n    }\r\n\r\n    Color4f opIndex(int x, int y)\r\n    {\r\n        while(x >= _width) x = _width-1;\r\n        while(y >= _height) y = _height-1;\r\n        while(x < 0) x = 0;\r\n        while(y < 0) y = 0;\r\n\r\n        float r, g, b, a;\r\n        auto dataptr = data.ptr + (y * _width + x) * _pixelSize;\r\n        memcpy(&r, dataptr, 4);\r\n        memcpy(&g, dataptr + 4, 4);\r\n        memcpy(&b, dataptr + 4 * 2, 4);\r\n        memcpy(&a, dataptr + 4 * 3, 4);\r\n        return Color4f(r, g, b, a);\r\n    }\r\n\r\n    Color4f opIndexAssign(Color4f c, int x, int y)\r\n    {\r\n        while(x >= _width) x = _width-1;\r\n        while(y >= _height) y = _height-1;\r\n        while(x < 0) x = 0;\r\n        while(y < 0) y = 0;\r\n\r\n        auto dataptr = data.ptr + (y * _width + x) * _pixelSize;\r\n        memcpy(dataptr, &c.arrayof[0], 4);\r\n        memcpy(dataptr + 4, &c.arrayof[1], 4);\r\n        memcpy(dataptr + 4 * 2, &c.arrayof[2], 4);\r\n        memcpy(dataptr + 4 * 3, &c.arrayof[3], 4);\r\n\r\n        return c;\r\n    }\r\n\r\n    protected void allocateData()\r\n    {\r\n        _data = new ubyte[_width * _height * _pixelSize];\r\n    }\n\n    void free()\r\n    {\r\n        // Do nothing, let GC delete the object\r\n    }\r\n\r\n    protected:\r\n\r\n    uint _width;\r\n    uint _height;\r\n    uint _bitDepth;\r\n    uint _channels;\r\n    uint _pixelSize;\r\n    ubyte[] _data;\r\n}\r\n\r\n/// Clamp pixels luminance to a specified range\r\nSuperImage clamp(SuperImage img, float minv, float maxv)\r\n{\r\n    foreach(x; 0..img.width)\r\n    foreach(y; 0..img.height)\r\n    {\r\n        img[x, y] = img[x, y].clamped(minv, maxv);\r\n    }\r\n\r\n    return img;\r\n}\r\n\r\n/**\r\n * Factory interface for HDR images\r\n */\r\ninterface SuperHDRImageFactory\r\n{\r\n    SuperHDRImage createImage(uint w, uint h);\r\n}\r\n\r\n/**\r\n * Factory class for HDR images\r\n */\r\nclass HDRImageFactory: SuperHDRImageFactory\r\n{\r\n    SuperHDRImage createImage(uint w, uint h)\r\n    {\r\n        return new HDRImage(w, h);\r\n    }\r\n}\r\n\r\nprivate SuperHDRImageFactory _defaultHDRImageFactory;\r\n\r\n/**\r\n * Get default SuperHDRImageFactory singleton\r\n */\r\nSuperHDRImageFactory defaultHDRImageFactory()\r\n{\r\n    if (!_defaultHDRImageFactory)\r\n        _defaultHDRImageFactory = new HDRImageFactory();\r\n    return _defaultHDRImageFactory;\r\n}\r\n\r\n/**\r\n * HDRImage that uses dlib.core.memory instead of GC\r\n */\r\nclass UnmanagedHDRImage: HDRImage\r\n{\r\n    override @property SuperImage dup()\r\n    {\r\n        auto res = New!(UnmanagedHDRImage)(_width, _height);\r\n        res.data[] = data[];\r\n        return res;\r\n    }\r\n\r\n    override SuperImage createSameFormat(uint w, uint h)\r\n    {\r\n        return New!(UnmanagedHDRImage)(w, h);\r\n    }\r\n\r\n    this(uint w, uint h)\r\n    {\r\n        super(w, h);\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        Delete(_data);\r\n    }\r\n\r\n    protected override void allocateData()\r\n    {\r\n        _data = New!(ubyte[])(_width * _height * _pixelSize);\r\n    }\n\n    override void free()\r\n    {\r\n        Delete(this);\r\n    }\r\n}\r\n\r\n/**\r\n * Factory class for UnmanagedHDRImageFactory\r\n */\r\nclass UnmanagedHDRImageFactory: SuperHDRImageFactory\r\n{\r\n    SuperHDRImage createImage(uint w, uint h)\r\n    {\r\n        return New!UnmanagedHDRImage(w, h);\r\n    }\r\n}\r\n\r\n/// Simple exponentiation tonal compression\r\nSuperImage hdrTonemapGamma(SuperHDRImage img, SuperImage output, float gamma)\r\n{\r\n    SuperImage res;\r\n    if (output)\r\n        res = output;\r\n    else\r\n        res = image(img.width, img.height, img.channels);\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        Color4f c = img[x, y];\r\n        float r = c.r ^^ gamma;\r\n        float g = c.g ^^ gamma;\r\n        float b = c.b ^^ gamma;\r\n        res[x, y] = Color4f(r, g, b, c.a);\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/// ditto\r\nSuperImage hdrTonemapGamma(SuperHDRImage img, float gamma)\r\n{\r\n    return hdrTonemapGamma(img, null, gamma);\r\n}\r\n\n/// Reinhard tonal compression\nSuperImage hdrTonemapReinhard(SuperHDRImage img, SuperImage output, float exposure, float gamma)\r\n{\r\n    SuperImage res;\r\n    if (output)\r\n        res = output;\r\n    else\r\n        res = image(img.width, img.height, img.channels);\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        Color4f c = img[x, y];\n        Vector3f v = c * exposure;\n        v = v / (v + 1.0f);\n        float r = v.r ^^ gamma;\r\n        float g = v.g ^^ gamma;\r\n        float b = v.b ^^ gamma;\r\n        res[x, y] = Color4f(r, g, b, c.a);\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/// ditto\r\nSuperImage hdrTonemapReinhard(SuperHDRImage img, float exposure, float gamma)\r\n{\r\n    return hdrTonemapReinhard(img, null, exposure, gamma);\r\n}\n\n/// Hable (Uncharted 2) tonal compression\nSuperImage hdrTonemapHable(SuperHDRImage img, SuperImage output, float exposure, float gamma)\r\n{\r\n    SuperImage res;\r\n    if (output)\r\n        res = output;\r\n    else\r\n        res = image(img.width, img.height, img.channels);\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        Color4f c = img[x, y];\n        Vector3f v = c * exposure;\n        Vector3f one = Vector3f(1.0f, 1.0f, 1.0f);\n        Vector3f W = Vector3f(11.2f, 11.2f, 11.2f);\n        v = hableFunc(v * 2.0f) * (one / hableFunc(W));\n        float r = v.r ^^ gamma;\r\n        float g = v.g ^^ gamma;\r\n        float b = v.b ^^ gamma;\r\n        res[x, y] = Color4f(r, g, b, c.a);\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/// ditto\r\nSuperImage hdrTonemapHable(SuperHDRImage img, float exposure, float gamma)\r\n{\r\n    return hdrTonemapHable(img, null, exposure, gamma);\r\n}\n\nVector3f hableFunc(Vector3f x)\n{\n   return ((x * (x * 0.15f + 0.1f * 0.5f) + 0.2f * 0.02f) / (x * (x * 0.15f + 0.5f) + 0.2f * 0.3f)) - 0.02f / 0.3f;\n}\n\r\n/// ACES curve tonal compression\nSuperImage hdrTonemapACES(SuperHDRImage img, SuperImage output, float exposure, float gamma)\r\n{\r\n    SuperImage res;\r\n    if (output)\r\n        res = output;\r\n    else\r\n        res = image(img.width, img.height, img.channels);\n\n    float a = 2.51;\n    float b = 0.03;\n    float c = 2.43;\n    float d = 0.59;\n    float e = 0.14;\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        Color4f col = img[x, y];\n        Color4f v = col * exposure * 0.6;\n        v = ((v * (v * a + b)) / (v * (v * c + d) + e)).clamped(0.0, 1.0);\n        res[x, y] = Color4f(\n            v.r ^^ gamma, \n            v.g ^^ gamma, \n            v.b ^^ gamma, \n            col.a);\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/// ditto\r\nSuperImage hdrTonemapACES(SuperHDRImage img, float exposure, float gamma)\r\n{\r\n    return hdrTonemapACES(img, null, exposure, gamma);\r\n}\n\r\n/// Average luminance tonal compression\nSuperImage hdrTonemapAverageLuminance(SuperHDRImage img, SuperImage output, float a, float gamma)\r\n{\r\n    SuperImage res;\r\n    if (output)\r\n        res = output;\r\n    else\r\n        res = image(img.width, img.height, img.channels);\n\n    float lumAverage = averageLuminance(img);\n    float aOverLumAverage = a / lumAverage;\n\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\n        auto col = img[x, y];\n        float Lw = col.luminance;\n        float L = Lw * aOverLumAverage;\n        float Ld = L / (1.0f + L);\n        Color4f nRGB = col / Lw;\n        Color4f dRGB = nRGB * Ld;\n        float r = dRGB.r ^^ gamma;\r\n        float g = dRGB.g ^^ gamma;\r\n        float b = dRGB.b ^^ gamma;\n        res[x, y] = Color4f(r, g, b, col.a);\n    }\n\r\n    return res;\r\n}\r\n\r\n/// ditto\r\nSuperImage hdrTonemapAverageLuminance(SuperHDRImage img, float a, float gamma)\r\n{\r\n    return hdrTonemapAverageLuminance(img, null, a, gamma);\r\n}\n\nfloat averageLuminance(SuperHDRImage img)\n{\n    float sumLuminance = 0.0f;\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\n        sumLuminance += log(EPSILON + img[x, y].luminance);        \r\n    }\n\n    float N = img.width * img.height;\n    float lumAverage = exp(sumLuminance / N); \n    return lumAverage;\n}\n"
  },
  {
    "path": "dlib/image/hsv.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * HSV color space\r\n *\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.hsv;\r\n\r\nimport std.algorithm;\r\nimport dlib.math.utils;\r\nimport dlib.image.color;\r\n\r\n/// HSV color channel\r\nenum HSVAChannel\r\n{\r\n    H = 0,\r\n    S = 1,\r\n    V = 2,\r\n    A = 3\r\n}\r\n\r\n/**\r\n * HSV floating-point color representation\r\n */\r\nstruct ColorHSVAf\r\n{\r\n    union\r\n    {\r\n        struct\r\n        {\r\n            float h;\r\n            float s;\r\n            float v;\r\n            float a;\r\n        }\r\n        float[4] arrayof;\r\n    }\r\n\r\n    this(float h, float s, float v, float a)\r\n    {\r\n        this.h = h;\r\n        this.s = s;\r\n        this.v = v;\r\n        this.a = a;\r\n    }\r\n\r\n    this(Color4f c)\r\n    {\r\n        a = c.a;\r\n\r\n        float cmin, cmax, delta;\r\n        cmin = min(c.r, c.g, c.b);\r\n        cmax = max(c.r, c.g, c.b);\r\n\r\n        v = cmax;\r\n        delta = cmax - cmin;\r\n\r\n        if (cmax > 0.0f)\r\n            s = delta / cmax;\r\n        else\r\n        {\r\n            // r = g = b = 0\r\n            // s = 0, h is undefined\r\n            s = 0.0f;\r\n            h = float.nan;\r\n            return;\r\n        }\r\n\r\n        if (c.r >= cmax)\r\n            h = (c.g - c.b) / delta;\r\n        else\r\n        {\r\n            if (c.g >= cmax)\r\n                h = 2.0f + (c.b - c.r) / delta;\r\n            else\r\n                h = 4.0f + (c.r - c.g) / delta;\r\n        }\r\n\r\n        h *= 60.0f;\r\n\r\n        if (h < 0.0f)\r\n            h += 360.0f;\r\n    }\r\n\r\n    Color4f rgba()\r\n    {\r\n        Color4f res;\r\n\r\n        res.a = a;\r\n\r\n        if (s <= 0.0f)\r\n        {\r\n            res.r = res.g = res.b = v;\r\n            return res;\r\n        }\r\n\r\n        float hh = h;\r\n\r\n        if (hh >= 360.0f)\r\n            hh = 0.0f;\r\n            hh /= 60.0f;\r\n\r\n        int i = cast(int)hh;\r\n        float ff = hh - i;\r\n        float p = v * (1.0f - s);\r\n        float q = v * (1.0f - (s * ff));\r\n        float t = v * (1.0f - (s * (1.0f - ff)));\r\n\r\n        switch(i)\r\n        {\r\n            case 0:  res.r = v; res.g = t; res.b = p; break;\r\n            case 1:  res.r = q; res.g = v; res.b = p; break;\r\n            case 2:  res.r = p; res.g = v; res.b = t; break;\r\n            case 3:  res.r = p; res.g = q; res.b = v; break;\r\n            case 4:  res.r = t; res.g = p; res.b = v; break;\r\n            case 5:\r\n            default: res.r = v; res.g = p; res.b = q; break;\r\n        }\r\n\r\n        return res;\r\n    }\r\n\r\n    void shiftHue(float degrees)\r\n    {\r\n        h += degrees;\r\n        while (h >= 360.0f)\r\n            h -= 360.0f;\r\n        while (h < 0.0f)\r\n            h += 360.0f;\r\n    }\r\n\r\n    void shiftSaturation(float val)\r\n    {\r\n        s += val;\r\n        s = clamp(s, 0.0f, 1.0f);\r\n    }\r\n\r\n    void scaleSaturation(float val)\r\n    {\r\n        s *= val;\r\n        s = clamp(s, 0.0f, 1.0f);\r\n    }\r\n\r\n    void shiftValue(float val)\r\n    {\r\n        v += val;\r\n        v = clamp(v, 0.0f, 1.0f);\r\n    }\r\n\r\n    void scaleValue(float val)\r\n    {\r\n        v *= val;\r\n        v = clamp(v, 0.0f, 1.0f);\r\n    }\r\n\r\n    bool hueInRange(float hue2, float tmin, float tmax)\r\n    {\r\n        if (h == hue2)\r\n            return true;\r\n\r\n        float h1 = hue2 + tmin;\r\n        while (h1 >= 360.0f)\r\n            h1 -= 360.0f;\r\n        while (h1 < 0.0f)\r\n            h1 += 360.0f;\r\n\r\n        float h2 = hue2 + tmax;\r\n        while (h2 >= 360.0f)\r\n            h2 -= 360.0f;\r\n        while (h1 < 0.0f)\r\n            h2 += 360.0f;\r\n\r\n        return (h1 > h2)?\r\n            (h > h1 || h < h2):\r\n            (h > h1 && h < h2);\r\n    }\r\n}\r\n\r\n/// RGBA from HSV[A] \r\nColor4f hsv(float h, float s, float v, float a = 1.0f)\r\n{\r\n    return ColorHSVAf(h, s, v, a).rgba;\r\n}\r\n"
  },
  {
    "path": "dlib/image/image.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Generic image interface and its implementations for integer pixel formats\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.image;\r\n\r\nimport std.stdio;\r\nimport std.math;\r\nimport std.conv;\r\nimport std.range;\r\nimport dlib.core.memory;\r\nimport dlib.math.vector;\r\nimport dlib.math.interpolation;\r\nimport dlib.image.color;\r\n\r\n/// sRGBa integer pixel formats, 8 and 16 bits per channel\r\nenum IntegerPixelFormat: uint\r\n{\r\n    L8 = 0,\r\n    LA8 = 1,\r\n    RGB8 = 2,\r\n    RGBA8 = 3,\r\n    L16 = 4,\r\n    LA16 = 5,\r\n    RGB16 = 6,\r\n    RGBA16 = 7\r\n}\r\n\r\n/**\r\n * Abstract image interface\r\n */\r\ninterface SuperImage: Freeable\r\n{\r\n    /**\r\n     * Image width in pixels\r\n     */\r\n    @property uint width();\r\n\r\n    /**\r\n     * Image height in pixels\r\n     */\r\n    @property uint height();\r\n    \r\n    /**\r\n     * Bits per channel\r\n     */\r\n    @property uint bitDepth();\r\n    \r\n    /**\r\n     * Number of channels\r\n     */\r\n    @property uint channels();\r\n    \r\n    /**\r\n     * Bytes per pixel\r\n     */\r\n    @property uint pixelSize();\n\n    /**\r\n     * This is compatible with IntegerPixelFormat and other internal format enums in dlib.\n     * Values from 0 to 255 are reserved for dlib.\n     * Values 256 and above are application-specific and can be used for custom SuperImage implementations\r\n     */\r\n    @property uint pixelFormat();\n\r\n    /**\r\n     * Returns raw buffer of image data in scan order.\r\n     * Pixel layout is specified by pixelFormat\r\n     */\r\n    @property ubyte[] data();\r\n\r\n    /**\r\n     * Pixel access operator.\r\n     * Should always return floating-point sRGBa or linear RGBa,\r\n     * depending on format family (IntegerPixelFormat or FloatPixelFormat)\r\n     */\r\n    Color4f opIndex(int x, int y);\r\n    \r\n    /**\r\n     * Pixel assignment operator.\r\n     * Accepts floating-point sRGBa or linear RGBa,\r\n     * depending on format family (IntegerPixelFormat or FloatPixelFormat)\r\n     */\r\n    Color4f opIndexAssign(Color4f c, int x, int y);\r\n\r\n    /**\r\n     * Makes a copy of the image\r\n     */\r\n    @property SuperImage dup();\r\n\r\n    /**\r\n     * Makes a blank image of the same format\r\n     */\r\n    SuperImage createSameFormat(uint w, uint h);\r\n\r\n    /**\r\n     * Range of x pixel indices\r\n     */\r\n    final @property auto row()\r\n    {\r\n        return iota(0, width);\r\n    }\r\n\r\n    /**\r\n     * Range of y pixel indices\r\n     */\r\n    final @property auto col()\r\n    {\r\n        return iota(0, height);\r\n    }\r\n\r\n    /**\r\n     * Enumerates all pixels of the image in scan order\r\n     */\r\n    final int opApply(scope int delegate(ref Color4f p, uint x, uint y) dg)\r\n    {\r\n        int result = 0;\r\n\r\n        foreach(uint y; col)\r\n        {\r\n            foreach(uint x; row)\r\n            {\r\n                Color4f col = opIndex(x, y);\r\n                result = dg(col, x, y);\r\n                opIndexAssign(col, x, y);\r\n\r\n                if (result)\r\n                    break;\r\n            }\r\n\r\n            if (result)\r\n                break;\r\n        }\r\n\r\n        return result;\r\n    }\r\n}\r\n\r\n/**\r\n * SuperImage implementation template for integer pixel formats\r\n */\r\nclass Image(IntegerPixelFormat fmt): SuperImage\r\n{\r\n    public:\r\n\r\n    override @property uint width()\r\n    {\r\n        return _width;\r\n    }\r\n\r\n    override @property uint height()\r\n    {\r\n        return _height;\r\n    }\r\n\r\n    override @property uint bitDepth()\r\n    {\r\n        return _bitDepth;\r\n    }\r\n\r\n    override @property uint channels()\r\n    {\r\n        return _channels;\r\n    }\r\n\r\n    override @property uint pixelSize()\r\n    {\r\n        return _pixelSize;\r\n    }\r\n\r\n    override @property uint pixelFormat()\r\n    {\r\n        return fmt;\r\n    }\r\n\r\n    override @property ubyte[] data()\r\n    {\r\n        return _data;\r\n    }\r\n\r\n    override @property SuperImage dup()\r\n    {\r\n        auto res = new Image!(fmt)(_width, _height);\r\n        res.data[] = data[];\r\n        return res;\r\n    }\r\n\r\n    override SuperImage createSameFormat(uint w, uint h)\r\n    {\r\n        return new Image!(fmt)(w, h);\r\n    }\r\n\r\n    this(uint w, uint h)\r\n    {\r\n        _width = w;\r\n        _height = h;\r\n\r\n        _bitDepth = [\r\n            IntegerPixelFormat.L8:     8, IntegerPixelFormat.LA8:     8,\r\n            IntegerPixelFormat.RGB8:   8, IntegerPixelFormat.RGBA8:   8,\r\n            IntegerPixelFormat.L16:   16, IntegerPixelFormat.LA16:   16,\r\n            IntegerPixelFormat.RGB16: 16, IntegerPixelFormat.RGBA16: 16\r\n        ][fmt];\r\n\r\n        _channels = [\r\n            IntegerPixelFormat.L8:    1, IntegerPixelFormat.LA8:    2,\r\n            IntegerPixelFormat.RGB8:  3, IntegerPixelFormat.RGBA8:  4,\r\n            IntegerPixelFormat.L16:   1, IntegerPixelFormat.LA16:   2,\r\n            IntegerPixelFormat.RGB16: 3, IntegerPixelFormat.RGBA16: 4\r\n        ][fmt];\r\n\r\n        _pixelSize = (_bitDepth / 8) * _channels;\r\n        \r\n        enum maxDimension = int.max;\r\n        \r\n        if (w > maxDimension)\r\n        {\r\n            writeln(\"Image data is not allocated. Exceeded maximum image width \", maxDimension);\r\n            return;\r\n        }\r\n        \r\n        if (h > maxDimension)\r\n        {\r\n            writeln(\"Image data is not allocated. Exceeded maximum image height \", maxDimension);\r\n            return;\r\n        }\r\n            \r\n        allocateData();\r\n    }\r\n\r\n    protected void allocateData()\r\n    {\r\n        size_t size = cast(size_t)_width * cast(size_t)_height * cast(size_t)_pixelSize;\r\n        _data = new ubyte[size];\r\n    }\r\n\r\n    public Color4 getPixel(int x, int y)\r\n    {\r\n        ubyte[] pixData = data();\r\n\r\n        if (x >= width) x = width-1;\r\n        else if (x < 0) x = 0;\r\n\r\n        if (y >= height) y = height-1;\r\n        else if (y < 0) y = 0;\r\n\r\n        size_t index = (cast(size_t)y * cast(size_t)_width + cast(size_t)x) * cast(size_t)_pixelSize;\r\n\r\n        auto maxv = (2 ^^ bitDepth) - 1;\r\n\r\n        static if (fmt == IntegerPixelFormat.L8)\r\n        {\r\n            auto v = pixData[index];\r\n            return Color4(v, v, v);\r\n        }\r\n        else if (fmt == IntegerPixelFormat.LA8)\r\n        {\r\n            auto v = pixData[index];\r\n            return Color4(v, v, v, pixData[index+1]);\r\n        }\r\n        else if (fmt == IntegerPixelFormat.RGB8)\r\n        {\r\n            return Color4(pixData[index], pixData[index+1], pixData[index+2], cast(ubyte)maxv);\r\n        }\r\n        else if (fmt == IntegerPixelFormat.RGBA8)\r\n        {\r\n            return Color4(pixData[index], pixData[index+1], pixData[index+2], pixData[index+3]);\r\n        }\r\n        else if (fmt == IntegerPixelFormat.L16)\r\n        {\r\n            ushort v = pixData[index] << 8 | pixData[index+1];\r\n            return Color4(v, v, v);\r\n        }\r\n        else if (fmt == IntegerPixelFormat.LA16)\r\n        {\r\n            ushort v = pixData[index]   << 8 | pixData[index+1];\r\n            ushort a = pixData[index+2] << 8 | pixData[index+3];\r\n            return Color4(v, v, v, a);\r\n        }\r\n        else if (fmt == IntegerPixelFormat.RGB16)\r\n        {\r\n            ushort r = pixData[index]   << 8 | pixData[index+1];\r\n            ushort g = pixData[index+2] << 8 | pixData[index+3];\r\n            ushort b = pixData[index+4] << 8 | pixData[index+5];\r\n            ushort a = cast(ushort)maxv;\r\n            return Color4(r, g, b, a);\r\n        }\r\n        else if (fmt == IntegerPixelFormat.RGBA16)\r\n        {\r\n            ushort r = pixData[index]   << 8 | pixData[index+1];\r\n            ushort g = pixData[index+2] << 8 | pixData[index+3];\r\n            ushort b = pixData[index+4] << 8 | pixData[index+5];\r\n            ushort a = pixData[index+6] << 8 | pixData[index+7];\r\n            return Color4(r, g, b, a);\r\n        }\r\n        else\r\n        {\r\n            assert (0, \"Image.opIndex is not implemented for IntegerPixelFormat.\" ~ to!string(fmt));\r\n        }\r\n    }\r\n\r\n    public Color4 setPixel(Color4 c, int x, int y)\r\n    {\r\n        ubyte[] pixData = data();\r\n\r\n        if (x >= width || y >= height || x < 0 || y < 0)\r\n            return c;\r\n\r\n        size_t index = (cast(size_t)y * cast(size_t)_width + cast(size_t)x) * cast(size_t)_pixelSize;\r\n\r\n        static if (fmt == IntegerPixelFormat.L8)\r\n        {\r\n            pixData[index] = cast(ubyte)c.r;\r\n        }\r\n        else if (fmt == IntegerPixelFormat.LA8)\r\n        {\r\n            pixData[index] = cast(ubyte)c.r;\r\n            pixData[index+1] = cast(ubyte)c.a;\r\n        }\r\n        else if (fmt == IntegerPixelFormat.RGB8)\r\n        {\r\n            pixData[index] = cast(ubyte)c.r;\r\n            pixData[index+1] = cast(ubyte)c.g;\r\n            pixData[index+2] = cast(ubyte)c.b;\r\n        }\r\n        else if (fmt == IntegerPixelFormat.RGBA8)\r\n        {\r\n            pixData[index] = cast(ubyte)c.r;\r\n            pixData[index+1] = cast(ubyte)c.g;\r\n            pixData[index+2] = cast(ubyte)c.b;\r\n            pixData[index+3] = cast(ubyte)c.a;\r\n        }\r\n        else if (fmt == IntegerPixelFormat.L16)\r\n        {\r\n            pixData[index] = cast(ubyte)(c.r >> 8);\r\n            pixData[index+1] = cast(ubyte)(c.r & 0xFF);\r\n        }\r\n        else if (fmt == IntegerPixelFormat.LA16)\r\n        {\r\n            pixData[index] = cast(ubyte)(c.r >> 8);\r\n            pixData[index+1] = cast(ubyte)(c.r & 0xFF);\r\n            pixData[index+2] = cast(ubyte)(c.a >> 8);\r\n            pixData[index+3] = cast(ubyte)(c.a & 0xFF);\r\n        }\r\n        else if (fmt == IntegerPixelFormat.RGB16)\r\n        {\r\n            pixData[index] = cast(ubyte)(c.r >> 8);\r\n            pixData[index+1] = cast(ubyte)(c.r & 0xFF);\r\n            pixData[index+2] = cast(ubyte)(c.g >> 8);\r\n            pixData[index+3] = cast(ubyte)(c.g & 0xFF);\r\n            pixData[index+4] = cast(ubyte)(c.b >> 8);\r\n            pixData[index+5] = cast(ubyte)(c.b & 0xFF);\r\n        }\r\n        else if (fmt == IntegerPixelFormat.RGBA16)\r\n        {\r\n            pixData[index] = cast(ubyte)(c.r >> 8);\r\n            pixData[index+1] = cast(ubyte)(c.r & 0xFF);\r\n            pixData[index+2] = cast(ubyte)(c.g >> 8);\r\n            pixData[index+3] = cast(ubyte)(c.g & 0xFF);\r\n            pixData[index+4] = cast(ubyte)(c.b >> 8);\r\n            pixData[index+5] = cast(ubyte)(c.b & 0xFF);\r\n            pixData[index+6] = cast(ubyte)(c.a >> 8);\r\n            pixData[index+7] = cast(ubyte)(c.a & 0xFF);\r\n        }\r\n        else\r\n        {\r\n            assert (0, \"Image.opIndexAssign is not implemented for IntegerPixelFormat.\" ~ to!string(fmt));\r\n        }\r\n\r\n        return c;\r\n    }\r\n\r\n    override Color4f opIndex(int x, int y)\r\n    {\r\n        return Color4f(getPixel(x, y), _bitDepth);\r\n    }\r\n\r\n    override Color4f opIndexAssign(Color4f c, int x, int y)\r\n    {\r\n        setPixel(c.convert(_bitDepth), x, y);\r\n        return c;\r\n    }\r\n\r\n    void free()\r\n    {\r\n        // Do nothing, let GC delete the object\r\n    }\r\n\r\n    protected:\r\n\r\n    uint _width;\r\n    uint _height;\r\n    uint _bitDepth;\r\n    uint _channels;\r\n    uint _pixelSize;\r\n    ubyte[] _data;\r\n}\r\n\r\n/// Specialization of Image for 8-bit luminance pixel format\r\nalias ImageL8 = Image!(IntegerPixelFormat.L8);\r\n/// Specialization of Image for 8-bit luminance-alpha pixel format\r\nalias ImageLA8 = Image!(IntegerPixelFormat.LA8);\r\n/// Specialization of Image for 8-bit RGB pixel format\r\nalias ImageRGB8 = Image!(IntegerPixelFormat.RGB8);\r\n/// Specialization of Image for 8-bit RGBA pixel format\r\nalias ImageRGBA8 = Image!(IntegerPixelFormat.RGBA8);\r\n\r\n/// Specialization of Image for 16-bit luminance pixel format\r\nalias ImageL16 = Image!(IntegerPixelFormat.L16);\r\n/// Specialization of Image for 16-bit luminance-alpha pixel format\r\nalias ImageLA16 = Image!(IntegerPixelFormat.LA16);\r\n/// Specialization of Image for 16-bit RGB pixel format\r\nalias ImageRGB16 = Image!(IntegerPixelFormat.RGB16);\r\n/// Specialization of Image for 16-bit RGBA pixel format\r\nalias ImageRGBA16 = Image!(IntegerPixelFormat.RGBA16);\r\n\r\n/**\r\n * All-in-one image factory interface\r\n */\r\ninterface SuperImageFactory\r\n{\r\n    SuperImage createImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1);\r\n}\r\n\r\n/**\r\n * All-in-one image factory class\r\n */\r\nclass ImageFactory: SuperImageFactory\r\n{\r\n    SuperImage createImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1)\r\n    {\r\n        return image(w, h, channels, bitDepth);\r\n    }\r\n}\r\n\r\nprivate SuperImageFactory _defaultImageFactory;\r\n\r\n/**\r\n * Get default image factory singleton\r\n */\r\nSuperImageFactory defaultImageFactory()\r\n{\r\n    if (!_defaultImageFactory)\r\n        _defaultImageFactory = new ImageFactory();\r\n    return _defaultImageFactory;\r\n}\r\n\r\n/// Create an image with specified parameters\r\nSuperImage image(uint w, uint h, uint channels = 3, uint bitDepth = 8)\r\nin\r\n{\r\n    assert(channels > 0 && channels <= 4);\r\n    assert(bitDepth == 8 || bitDepth == 16);\r\n}\r\ndo\r\n{\r\n    switch(channels)\r\n    {\r\n        case 1:\r\n        {\r\n            if (bitDepth == 8)\r\n                return new ImageL8(w, h);\r\n            else\r\n                return new ImageL16(w, h);\r\n        }\r\n        case 2:\r\n        {\r\n            if (bitDepth == 8)\r\n                return new ImageLA8(w, h);\r\n            else\r\n                return new ImageLA16(w, h);\r\n        }\r\n        case 3:\r\n        {\r\n            if (bitDepth == 8)\r\n                return new ImageRGB8(w, h);\r\n            else\r\n                return new ImageRGB16(w, h);\r\n        }\r\n        case 4:\r\n        {\r\n            if (bitDepth == 8)\r\n                return new ImageRGBA8(w, h);\r\n            else\r\n                return new ImageRGBA16(w, h);\r\n        }\r\n        default:\r\n            assert(0);\r\n    }\r\n}\r\n\r\n/// Convert image to specified pixel format\r\nT convert(T)(SuperImage img)\r\n{\r\n    auto res = new T(img.width, img.height);\r\n    foreach(x; 0..img.width)\r\n    foreach(y; 0..img.height)\r\n        res[x, y] = img[x, y];\r\n    return res;\r\n}\r\n\r\n/// Get interpolated pixel value from an image\r\nColor4f bilinearPixel(SuperImage img, float x, float y)\r\n{\r\n    real intX;\r\n    real fracX = modf(x, intX);\r\n    real intY;\r\n    real fracY = modf(y, intY);\r\n\r\n    Color4f c1 = img[cast(int)intX, cast(int)intY];\r\n    Color4f c2 = img[cast(int)(intX + 1.0f), cast(int)intY];\r\n    Color4f c3 = img[cast(int)(intX + 1.0f), cast(int)(intY + 1.0f)];\r\n    Color4f c4 = img[cast(int)intX, cast(int)(intY + 1.0f)];\r\n\r\n    Color4f ic1 = lerp(c1, c2, fracX);\r\n    Color4f ic2 = lerp(c4, c3, fracX);\r\n    Color4f ic3 = lerp(ic1, ic2, fracY);\r\n\r\n    return ic3;\r\n}\r\n\r\n/**\r\n * Rectangular region of an image that can be iterated with foreach\r\n */\r\nstruct ImageRegion\r\n{\r\n    SuperImage img;\r\n    uint xstart;\r\n    uint ystart;\r\n    uint width;\r\n    uint height;\r\n\r\n    final int opApply(scope int delegate(ref Color4f p, uint x, uint y) dg)\r\n    {\r\n        int result = 0;\r\n        uint x1, y1;\r\n\r\n        foreach(uint y; 0..height)\r\n        {\r\n            y1 = ystart + y;\r\n            foreach(uint x; 0..width)\r\n            {\r\n                x1 = xstart + x;\r\n                Color4f col = img[x1, y1];\r\n                result = dg(col, x, y);\r\n                img[x1, y1] = col;\r\n\r\n                if (result)\r\n                    break;\r\n            }\r\n\r\n            if (result)\r\n                break;\r\n        }\r\n\r\n        return result;\r\n    }\r\n}\r\n\r\n/// ImageRegion factory function\r\nImageRegion region(SuperImage img, uint x, uint y, uint width, uint height)\r\n{\r\n    return ImageRegion(img, x, y, width, height);\r\n}\r\n\r\n/**\r\n An InputRange of windows (regions around pixels) of an image that can be iterated with foreach\r\n */\r\nstruct ImageWindowRange\r\n{\r\n    SuperImage img;\r\n    uint width;\r\n    uint height;\r\n\r\n    private uint halfWidth;\r\n    private uint halfHeight;\r\n    private uint wx = 0;\r\n    private uint wy = 0;\r\n\r\n    this(SuperImage img, uint w, uint h)\r\n    {\r\n        this.img = img;\r\n        this.width = w;\r\n        this.height = h;\r\n\r\n        this.halfWidth = this.width / 2;\r\n        this.halfHeight = this.height / 2;\r\n    }\r\n\r\n    final int opApply(scope int delegate(ImageRegion w, uint x, uint y) dg)\r\n    {\r\n        int result = 0;\r\n\r\n        foreach(uint y; img.col)\r\n        {\r\n            uint ystart = y - halfWidth;\r\n            foreach(uint x; img.row)\r\n            {\r\n                uint xstart = x - halfHeight;\r\n\r\n                auto window = region(img, xstart, ystart, width, height);\r\n                result = dg(window, x, y);\r\n\r\n                if (result)\r\n                    break;\r\n            }\r\n\r\n            if (result)\r\n                break;\r\n        }\r\n\r\n        return result;\r\n    }\r\n\r\n    bool empty = false;\r\n\r\n    void popFront()\r\n    {\r\n        wx++;\r\n        if (wx == img.width)\r\n        {\r\n            wx = 0;\r\n            wy++;\r\n\r\n            if (wy == img.height)\r\n            {\r\n                wy = 0;\r\n                empty = true;\r\n            }\r\n        }\r\n    }\r\n\r\n    @property ImageRegion front()\r\n    {\r\n        return region(img, wx - halfWidth, wy - halfHeight, width, height);\r\n    }\r\n}\r\n\r\n/**\r\n ImageWindowRange factory function\r\n \r\n Examples:\r\n ---\r\n // Convolution with emboss kernel\r\n \r\n float[3][3] kernel = [\r\n     [-1, -1,  0],\r\n     [-1,  0,  1],\r\n     [ 0,  1,  1],\r\n ];\r\n\r\n foreach(window, x, y; inputImage.windows(3, 3))\r\n {\r\n     Color4f sum = Color4f(0, 0, 0);\r\n     foreach(ref Color4f pixel, x, y; window)\r\n         sum += pixel * kernel[y][x];\r\n     outputImage[x, y] = sum / 4.0f + 0.5f;\r\n }\r\n ---\r\n */\r\nImageWindowRange windows(SuperImage img, uint width, uint height)\r\n{\r\n    return ImageWindowRange(img, width, height);\r\n}\r\n"
  },
  {
    "path": "dlib/image/io/bmp.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Timur Gafarov, Roman Chistokhodov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Decode and encode BMP images\r\n *\r\n * Copyright: Timur Gafarov, Roman Chistokhodov 2014-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov, Roman Chistokhodov\r\n */\r\nmodule dlib.image.io.bmp;\r\n\r\nimport std.stdio;\r\nimport dlib.core.stream;\r\nimport dlib.core.memory;\r\nimport dlib.core.compound;\r\nimport dlib.image.image;\r\nimport dlib.image.color;\r\nimport dlib.image.io;\r\nimport dlib.image.io.utils;\r\nimport dlib.filesystem.local;\r\n\r\n// uncomment this to see debug messages:\r\n//version = BMPDebug;\r\n\r\nstatic const ubyte[2] BMPMagic = ['B', 'M'];\r\n\r\nstruct BMPFileHeader\r\n{\r\n    ubyte[2] type;        // magic number \"BM\"\r\n    uint size;            // file size\r\n    ushort reserved1;\r\n    ushort reserved2;\r\n    uint offset;          // offset to image data\r\n}\r\n\r\nstruct BMPInfoHeader\r\n{\r\n    uint size;            // size of bitmap info header\r\n    int width;            // image width\r\n    int height;           // image height\r\n    ushort planes;        // must be equal to 1\r\n    ushort bitsPerPixel;  // bits per pixel\r\n    uint compression;     // compression type\r\n    uint imageSize;       // size of pixel data\r\n    int xPixelsPerMeter;  // pixels per meter on x-axis\r\n    int yPixelsPerMeter;  // pixels per meter on y-axis\r\n    uint colorsUsed;      // number of used colors\r\n    uint colorsImportant; // number of important colors\r\n}\r\n\r\nstruct BMPCoreHeader\r\n{\r\n    uint size;            // size of bitmap core header\r\n    ushort width;         // image with\r\n    ushort height;        // image height\r\n    ushort planes;        // must be equal to 1\r\n    ushort bitsPerPixel;  // bits per pixel\r\n}\r\n\r\nstruct BMPCoreInfo\r\n{\r\n    BMPCoreHeader header;\r\n    ubyte[3] colors;\r\n}\r\n\r\nenum BMPOSType\r\n{\r\n    Win,\r\n    OS2\r\n}\r\n\r\n// BMP compression type constants\r\nenum BMPCompressionType\r\n{\r\n    RGB          = 0,\r\n    RLE8         = 1,\r\n    RLE4         = 2,\r\n    BitFields    = 3\r\n}\r\n\r\n// RLE byte type constants\r\nenum RLE\r\n{\r\n    Command      = 0,\r\n    EndOfLine    = 0,\r\n    EndOfBitmap  = 1,\r\n    Delta        = 2\r\n}\r\n\r\nenum BMPInfoSize\r\n{\r\n    OLD  = 12,\r\n    WIN  = 40,\r\n    OS2  = 64,\r\n    WIN4 = 108,\r\n    WIN5 = 124,\r\n}\r\n\r\nclass BMPLoadException: ImageLoadException\r\n{\r\n    this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)\r\n    {\r\n        super(msg, file, line, next);\r\n    }\r\n}\r\n\r\nprivate ubyte calculateShift(uint mask) nothrow pure\r\n{\r\n    ubyte result = 0;\r\n    while (mask && !(mask & 1)) {\r\n        result++;\r\n        mask >>= 1;\r\n    }\r\n    return result;\r\n}\r\n\r\nunittest\r\n{\r\n    assert(calculateShift(0xff) == 0);\r\n    assert(calculateShift(0xff00) == 8);\r\n    assert(calculateShift(0xff0000) == 16);\r\n    assert(calculateShift(0xff000000) == 24);\r\n}\r\n\r\nprivate ubyte applyMask(uint value, uint mask, ubyte shift, ubyte scale) nothrow pure\r\n{\r\n    return cast(ubyte) (((value & mask) >> shift) * scale);\r\n}\r\n\r\nprivate ubyte calculateScale(uint mask, ubyte shift) nothrow pure\r\n{\r\n    return cast(ubyte) (256 / calculateDivisor(mask, shift));\r\n}\r\n\r\nprivate uint calculateDivisor(uint mask, ubyte shift) nothrow pure\r\n{\r\n    return (mask >> shift) + 1;\r\n}\r\n\r\nprivate bool checkIndex(uint index, const(ubyte)[] colormap) nothrow pure {\r\n    return index + 2 < colormap.length;\r\n}\r\n\r\n/**\r\n * Load BMP from file using local FileSystem.\r\n * Causes GC allocation\r\n */\r\nSuperImage loadBMP(string filename)\r\n{\r\n    InputStream input = openForInput(filename);\r\n\r\n    try\r\n    {\r\n        ubyte[] data = New!(ubyte[])(cast(size_t)input.size);\r\n        input.fillArray(data);\r\n        ArrayStream arrStrm = New!ArrayStream(data);\r\n        auto img = loadBMP(arrStrm);\r\n        Delete(arrStrm);\r\n        Delete(data);\r\n        return img;\r\n    }\r\n    catch (BMPLoadException ex)\r\n    {\r\n        throw new Exception(\"'\" ~ filename ~ \"' :\" ~ ex.msg, ex.file, ex.line, ex.next);\r\n    }\r\n    finally\r\n    {\r\n        input.close();\r\n    }\r\n}\r\n\r\n/**\r\n * Load BMP from stream using default image factory.\r\n * Causes GC allocation\r\n */\r\nSuperImage loadBMP(InputStream istrm)\r\n{\r\n    Compound!(SuperImage, string) res =\r\n        loadBMP(istrm, defaultImageFactory);\r\n    if (res[0] is null)\r\n        throw new BMPLoadException(res[1]);\r\n    else\r\n        return res[0];\r\n}\r\n\r\n/**\r\n * Load BMP from stream using specified image factory.\r\n * GC-free\r\n */\r\nCompound!(SuperImage, string) loadBMP(\r\n    InputStream istrm,\r\n    SuperImageFactory imgFac)\r\n{\r\n    SuperImage img = null;\r\n\r\n    BMPFileHeader bmpfh;\r\n    BMPInfoHeader bmpih;\r\n    BMPCoreHeader bmpch;\r\n\r\n    BMPOSType osType;\r\n\r\n    uint compression;\r\n    uint bitsPerPixel;\r\n\r\n    uint redMask, greenMask, blueMask, alphaMask;\r\n\r\n    ubyte[] colormap;\r\n    int colormapSize;\r\n\r\n    Compound!(SuperImage, string) error(string errorMsg)\r\n    {\r\n        if (img)\r\n        {\r\n            img.free();\r\n            img = null;\r\n        }\r\n        if (colormap.length)\r\n            Delete(colormap);\r\n        return compound(img, errorMsg);\r\n    }\r\n\r\n    bmpfh = readStruct!BMPFileHeader(istrm);\r\n\r\n    auto bmphPos = istrm.position;\r\n\r\n    version(BMPDebug)\r\n    {\r\n        writefln(\"bmpfh.type = %s\", cast(char[])bmpfh.type);\r\n        writefln(\"bmpfh.size = %s\", bmpfh.size);\r\n        writefln(\"bmpfh.reserved1 = %s\", bmpfh.reserved1);\r\n        writefln(\"bmpfh.reserved2 = %s\", bmpfh.reserved2);\r\n        writefln(\"bmpfh.offset = %s\", bmpfh.offset);\r\n        writeln(\"-------------------\");\r\n    }\r\n\r\n    if (bmpfh.type != BMPMagic)\r\n        return error(\"loadBMP error: input data is not BMP\");\r\n\r\n    uint numChannels = 3;\r\n    uint width, height;\r\n\r\n    bmpih = readStruct!BMPInfoHeader(istrm);\r\n\r\n    version(BMPDebug)\r\n    {\r\n        writefln(\"bmpih.size = %s\", bmpih.size);\r\n        writefln(\"bmpih.width = %s\", bmpih.width);\r\n        writefln(\"bmpih.height = %s\", bmpih.height);\r\n        writefln(\"bmpih.planes = %s\", bmpih.planes);\r\n        writefln(\"bmpih.bitsPerPixel = %s\", bmpih.bitsPerPixel);\r\n        writefln(\"bmpih.compression = %s\", bmpih.compression);\r\n        writefln(\"bmpih.imageSize = %s\", bmpih.imageSize);\r\n        writefln(\"bmpih.xPixelsPerMeter = %s\", bmpih.xPixelsPerMeter);\r\n        writefln(\"bmpih.yPixelsPerMeter = %s\", bmpih.yPixelsPerMeter);\r\n        writefln(\"bmpih.colorsUsed = %s\", bmpih.colorsUsed);\r\n        writefln(\"bmpih.colorsImportant = %s\", bmpih.colorsImportant);\r\n        writeln(\"-------------------\");\r\n    }\r\n\r\n    if (bmpih.compression > 3)\r\n    {\r\n        /*\r\n         * This is an OS/2 bitmap file, we don't use\r\n         * bitmap info header but bitmap core header instead\r\n         */\r\n\r\n        // We must go back to read bitmap core header\r\n        istrm.position = bmphPos;\r\n        bmpch = readStruct!BMPCoreHeader(istrm);\r\n\r\n        osType = BMPOSType.OS2;\r\n        compression = BMPCompressionType.RGB;\r\n        bitsPerPixel = bmpch.bitsPerPixel;\r\n\r\n        width = bmpch.width;\r\n        height = bmpch.height;\r\n    }\r\n    else\r\n    {\r\n        // Windows style\r\n        osType = BMPOSType.Win;\r\n        compression = bmpih.compression;\r\n        bitsPerPixel = bmpih.bitsPerPixel;\r\n\r\n        width = bmpih.width;\r\n        height = bmpih.height;\r\n    }\r\n\r\n    version(BMPDebug)\r\n    {\r\n        writefln(\"osType = %s\", [BMPOSType.OS2: \"OS/2\", BMPOSType.Win: \"Windows\"][osType]);\r\n        writefln(\"width = %s\", width);\r\n        writefln(\"height = %s\", height);\r\n        writefln(\"bitsPerPixel = %s\", bitsPerPixel);\r\n        writefln(\"compression = %s\", compression);\r\n        writeln(\"-------------------\");\r\n    }\r\n\r\n    if (bmpih.size >= BMPInfoSize.WIN4 || (compression == BMPCompressionType.BitFields && (bitsPerPixel == 16 || bitsPerPixel == 32)))\r\n    {\r\n        bool ok = true;\r\n        ok = ok && istrm.readLE(&redMask);\r\n        ok = ok && istrm.readLE(&greenMask);\r\n        ok = ok && istrm.readLE(&blueMask);\r\n\r\n        version(BMPDebug)\r\n        {\r\n            writeln(\"File has bitfields masks\");\r\n            writefln(\"redMask = %#x\", redMask);\r\n            writefln(\"greenMask = %#x\", greenMask);\r\n            writefln(\"blueMask = %#x\", blueMask);\r\n            writeln(\"-------------------\");\r\n        }\r\n\r\n        if (ok && bmpih.size >= BMPInfoSize.WIN4)\r\n        {\r\n            version(BMPDebug)\r\n            {\r\n                writeln(\"File is at least version 4\");\r\n            }\r\n\r\n            int CSType;\r\n            int[9] coords;\r\n            int gammaRed;\r\n            int gammaGreen;\r\n            int gammaBlue;\r\n\r\n            ok = ok && istrm.readLE(&alphaMask);\r\n            ok = ok && istrm.readLE(&CSType);\r\n            istrm.fillArray(coords);\r\n            ok = ok && istrm.readLE(&gammaRed);\r\n            ok = ok && istrm.readLE(&gammaGreen);\r\n            ok = ok && istrm.readLE(&gammaBlue);\r\n\r\n            if (ok && bmpih.size >= BMPInfoSize.WIN5)\r\n            {\r\n                version(BMPDebug)\r\n                {\r\n                    writeln(\"File is at least version 5\");\r\n                }\r\n\r\n                int intent;\r\n                int profileData;\r\n                int profileSize;\r\n                int reserved;\r\n\r\n                ok = ok && istrm.readLE(&intent);\r\n                ok = ok && istrm.readLE(&profileData);\r\n                ok = ok && istrm.readLE(&profileSize);\r\n                ok = ok && istrm.readLE(&reserved);\r\n            }\r\n        }\r\n        if (!ok)\r\n            return error(\"loadBMP error: failed to read data of size specified in bmp info structure\");\r\n    }\r\n\r\n    if (compression != BMPCompressionType.RGB && compression != BMPCompressionType.BitFields && compression != BMPCompressionType.RLE8)\r\n        return error(\"loadBMP error: unsupported compression type (RLE4 is not supported yet)\");\r\n\r\n    if (bitsPerPixel != 4 && bitsPerPixel != 8 && bitsPerPixel != 16 && bitsPerPixel != 24 && bitsPerPixel != 32)\r\n        return error(\"loadBMP error: unsupported color depth\");\r\n\r\n    uint numberOfColors;\r\n    ubyte colormapEntrySize = (osType == BMPOSType.OS2)? 3 : 4;\r\n\r\n    ubyte blueShift, greenShift, redShift, alphaShift;\r\n    ubyte blueScale = 1, greenScale = 1, redScale = 1, alphaScale;\r\n\r\n    if (bitsPerPixel == 8 || bitsPerPixel == 4)\r\n    {\r\n        numberOfColors = bmpih.colorsUsed ? bmpih.colorsUsed : (1 << bitsPerPixel);\r\n        if (numberOfColors == 0 || numberOfColors > 256)\r\n            return error(\"loadBMP error: strange number of used colors\");\r\n    }\r\n    else if (compression == BMPCompressionType.BitFields && (bitsPerPixel == 16 || bitsPerPixel == 32))\r\n    {\r\n        redShift = calculateShift(redMask);\r\n        greenShift = calculateShift(greenMask);\r\n        blueShift = calculateShift(blueMask);\r\n        alphaShift = calculateShift(alphaMask);\r\n\r\n        version(BMPDebug)\r\n        {\r\n            writefln(\"redShift = %#x\", redShift);\r\n            writefln(\"greenShift = %#x\", greenShift);\r\n            writefln(\"blueShift = %#x\", blueShift);\r\n            writefln(\"alphaShift = %#x\", alphaShift);\r\n        }\r\n\r\n        //scales are used to get equivalent weights for every color channel fit in byte\r\n\r\n        if (calculateDivisor(redMask, redShift) == 0 || calculateDivisor(greenMask, greenShift) == 0 ||\r\n            calculateDivisor(blueMask, blueShift) == 0 || calculateDivisor(alphaMask, alphaShift) == 0)\r\n            return error(\"loadBMP error: division by zero when calculating scale\");\r\n\r\n        redScale = calculateScale(redMask, redShift);\r\n        greenScale = calculateScale(greenMask, greenShift);\r\n        blueScale = calculateScale(blueMask, blueShift);\r\n        alphaScale = calculateScale(alphaMask, alphaShift);\r\n\r\n        version(BMPDebug)\r\n        {\r\n            writefln(\"redScale = %#x\", redScale);\r\n            writefln(\"greenScale = %#x\", greenScale);\r\n            writefln(\"blueScale = %#x\", blueScale);\r\n            writefln(\"alphaScale = %#x\", alphaScale);\r\n        }\r\n    }\r\n    else if (compression == BMPCompressionType.RGB && (bitsPerPixel == 24 || bitsPerPixel == 32))\r\n    {\r\n        blueMask = 0x000000ff;\r\n        greenMask = 0x0000ff00;\r\n        redMask = 0x00ff0000;\r\n        blueShift = 0;\r\n        greenShift = 8;\r\n        redShift = 16;\r\n    }\r\n    else if (compression == BMPCompressionType.RGB && bitsPerPixel == 16)\r\n    {\r\n        blueMask = 0x001f;\r\n        greenMask = 0x03e0;\r\n        redMask = 0x7c00;\r\n        blueShift = 0;\r\n        greenShift = 2;\r\n        redShift = 7;\r\n        blueScale = 8;\r\n    }\r\n    else\r\n        return error(\"loadBMP error: unknown compression type / color depth combination\");\r\n\r\n    // Look for palette data if present\r\n    if (numberOfColors)\r\n    {\r\n        colormapSize = numberOfColors * colormapEntrySize;\r\n        colormap = New!(ubyte[])(colormapSize);\r\n        istrm.fillArray(colormap);\r\n    }\r\n\r\n    // Go to begining of pixel data\r\n    istrm.position = bmpfh.offset;\r\n\r\n    const bool transparent = alphaMask != 0 && compression == BMPCompressionType.BitFields;\r\n\r\n    // Create image\r\n    img = imgFac.createImage(width, height, transparent ? 4 : 3, 8);\r\n\r\n    enum wrongIndexError = \"wrong index for colormap\";\r\n\r\n    if (bitsPerPixel == 4 && compression == BMPCompressionType.RGB)\r\n    {\r\n        foreach(y; 0..img.height)\r\n        {\r\n            //4 bits per pixel, so width/2 iterations\r\n            foreach(x; 0..img.width/2)\r\n            {\r\n                ubyte[1] buf;\r\n                istrm.fillArray(buf);\r\n                const uint first = (buf[0] >> 4)*colormapEntrySize;\r\n                const uint second = (buf[0] & 0x0f)*colormapEntrySize;\r\n                \r\n                if (!checkIndex(first, colormap) || !checkIndex(second, colormap))\r\n                    return error(wrongIndexError);\r\n                \r\n                img[x*2, img.height-y-1] = Color4f(ColorRGBA(colormap[first+2], colormap[first+1], colormap[first]));\r\n                img[x*2 + 1, img.height-y-1] = Color4f(ColorRGBA(colormap[second+2], colormap[second+1], colormap[second]));\r\n            }\r\n            //for odd widths\r\n            if (img.width & 1)\r\n            {\r\n                ubyte[1] buf;\r\n                istrm.fillArray(buf);\r\n                const uint index = (buf[0] >> 4)*colormapEntrySize;\r\n                if (!checkIndex(index, colormap))\r\n                    return error(wrongIndexError);\r\n                img[img.width-1, img.height-y-1] = Color4f(ColorRGBA(colormap[index+2], colormap[index+1], colormap[index]));\r\n            }\r\n        }\r\n    }\r\n    else if (bitsPerPixel == 8 && compression == BMPCompressionType.RGB)\r\n    {\r\n        foreach(y; 0..img.height)\r\n        {\r\n            foreach(x; 0..img.width)\r\n            {\r\n                ubyte[1] buf;\r\n                istrm.fillArray(buf);\r\n                const uint index = buf[0]*colormapEntrySize;\r\n                if (!checkIndex(index, colormap))\r\n                    return error(wrongIndexError);\r\n                img[x, img.height-y-1] = Color4f(ColorRGBA(colormap[index+2], colormap[index+1], colormap[index]));\r\n            }\r\n        }\r\n    }\r\n    else if (bitsPerPixel == 8 && compression == BMPCompressionType.RLE8)\r\n    {\r\n        int x, y;\r\n        \r\n        while(y < img.height)\r\n        {\r\n            ubyte value;\r\n            \r\n            if (!istrm.readLE(&value))\r\n                break;\r\n            \r\n            if (value == 0)\r\n            {\r\n                if (!istrm.readLE(&value) || value == 1)\r\n                    break;\r\n                else\r\n                {\r\n                    if (value == 0)\r\n                    {\r\n                        x = 0;\r\n                        y++;\r\n                    }\r\n                    else if (value == 2)\r\n                    {\r\n                        version(BMPDebug) writeln(\"in delta\");\r\n                        \r\n                        ubyte xdelta, ydelta;\r\n                        istrm.readLE(&xdelta);\r\n                        istrm.readLE(&ydelta);\r\n                        x += xdelta;\r\n                        y += ydelta;\r\n                    }\r\n                    else\r\n                    {\r\n                        version(BMPDebug) writeln(\"in absolute mode\");\r\n                        \r\n                        foreach(i; 0..value)\r\n                        {\r\n                            ubyte j;\r\n                            istrm.readLE(&j);\r\n                            const uint index = j*colormapEntrySize;\r\n                            if (!checkIndex(index, colormap))\r\n                                return error(wrongIndexError);\r\n                            img[x++, img.height-y-1] = Color4f(ColorRGBA(colormap[index+2], colormap[index+1], colormap[index]));\r\n                        }\r\n                        if (value & 1)\r\n                        {\r\n                            ubyte padding;\r\n                            istrm.readLE(&padding);\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            else\r\n            {\r\n                ubyte j;\r\n                istrm.readLE(&j);\r\n                const uint index = j*colormapEntrySize;\r\n                if (!checkIndex(index, colormap))\r\n                    return error(wrongIndexError);\r\n                \r\n                foreach(i; 0..value)\r\n                    img[x++, img.height-y-1] = Color4f(ColorRGBA(colormap[index+2], colormap[index+1], colormap[index]));\r\n            }\r\n        }\r\n    }\r\n    else if (bitsPerPixel == 16 || bitsPerPixel == 24 || bitsPerPixel == 32)\r\n    {\r\n        const bytesPerPixel = bitsPerPixel / 8;\r\n        const bytesPerRow = ((bitsPerPixel*width+31)/32)*4; //round to multiple of 4\r\n        const bytesPerLine = bytesPerPixel * width;\r\n        const padding = bytesPerRow - bytesPerLine;\r\n\r\n        if (bitsPerPixel == 24)\r\n        {\r\n            foreach(y; 0..img.height)\r\n            {\r\n                foreach(x; 0..img.width)\r\n                {\r\n                    ubyte[3] bgr;\r\n                    istrm.fillArray(bgr);\r\n                    img[x, img.height-y-1] = Color4f(ColorRGBA(bgr[2], bgr[1], bgr[0]));\r\n                }\r\n\r\n                istrm.seek(padding);\r\n            }\r\n        }\r\n        else if (bitsPerPixel == 16)\r\n        {\r\n            foreach(y; 0..img.height)\r\n            {\r\n                foreach(x; 0..img.width)\r\n                {\r\n                    ushort bgr;\r\n                    istrm.readLE(&bgr);\r\n                    const uint p = bgr;\r\n                    const ubyte r = applyMask(p, redMask, redShift, redScale);\r\n                    const ubyte g = applyMask(p, greenMask, greenShift, greenScale);\r\n                    const ubyte b = applyMask(p, blueMask, blueShift, blueScale);\r\n\r\n                    img[x, img.height-y-1] = Color4f(ColorRGBA(r,g,b));\r\n                }\r\n\r\n                istrm.seek(padding);\r\n            }\r\n        }\r\n        else if (bitsPerPixel == 32)\r\n        {\r\n            foreach(y; 0..img.height)\r\n            {\r\n                foreach(x; 0..img.width)\r\n                {\r\n                    uint p;\r\n                    istrm.readLE(&p);\r\n\r\n                    const ubyte r = applyMask(p, redMask, redShift, redScale);\r\n                    const ubyte g = applyMask(p, greenMask, greenShift, greenScale);\r\n                    const ubyte b = applyMask(p, blueMask, blueShift, blueScale);\r\n\r\n                    img[x, img.height-y-1] = Color4f(ColorRGBA(r, g, b, transparent ? applyMask(p, alphaMask, alphaShift, alphaScale) : 0xff));\r\n                }\r\n\r\n                istrm.seek(padding);\r\n            }\r\n        }\r\n    }\r\n    else\r\n        return error(\"loadBMP error: unknown or unsupported compression type / color depth combination\");\r\n\r\n    if (colormap.length)\r\n        Delete(colormap);\r\n\r\n    return compound(img, \"\");\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    import dlib.core.stream;\r\n    import std.stdio;\r\n\r\n    SuperImage img;\r\n\r\n    //32 bit with bitfield masks\r\n    ubyte[] bmpData32 =\r\n    [\r\n        66, 77, 72, 1, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 56, 0, 0, 0, 8, 0, 0, 0, 8, 0,\r\n        0, 0, 1, 0, 32, 0, 3, 0, 0, 0, 2, 1, 0, 0, 18, 11, 0, 0, 18, 11, 0, 0, 0, 0, 0,\r\n        0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,\r\n        255, 255, 0, 245, 235, 224, 0, 229, 199, 154, 0, 248, 227, 185, 0, 255, 229,\r\n        181, 0, 236, 203, 152, 0, 244, 234, 223, 0, 255, 255, 255, 0, 244, 234, 224, 0,\r\n        202, 139, 76, 0, 242, 199, 126, 0, 202, 217, 187, 0, 117, 190, 218, 0, 167,\r\n        177, 160, 0, 209, 140, 72, 0, 243, 231, 221, 0, 196, 149, 107, 0, 166, 97, 16,\r\n        0, 208, 143, 34, 0, 161, 188, 160, 0, 59, 207, 255, 0, 52, 168, 228, 0, 182,\r\n        115, 42, 0, 196, 144, 97, 0, 196, 151, 116, 0, 192, 136, 51, 0, 226, 169, 71,\r\n        0, 231, 202, 160, 0, 170, 199, 178, 0, 101, 178, 172, 0, 176, 156, 116, 0, 201,\r\n        153, 112, 0, 204, 162, 127, 0, 185, 156, 134, 0, 136, 155, 170, 0, 153, 201,\r\n        201, 0, 161, 211, 186, 0, 69, 179, 136, 0, 123, 151, 103, 0, 210, 164, 133, 0,\r\n        215, 183, 153, 0, 201, 174, 166, 0, 34, 94, 208, 0, 29, 132, 228, 0, 125, 188,\r\n        190, 0, 112, 178, 134, 0, 120, 144, 104, 0, 213, 181, 154, 0, 246, 240, 233, 0,\r\n        221, 193, 168, 0, 167, 168, 213, 0, 127, 147, 220, 0, 220, 224, 236, 0, 255,\r\n        239, 232, 0, 220, 191, 169, 0, 245, 238, 230, 0, 255, 255, 255, 0, 247, 240,\r\n        233, 0, 235, 213, 186, 0, 252, 237, 216, 0, 245, 231, 217, 0, 231, 212, 193, 0,\r\n        246, 239, 230, 0, 255, 255, 255, 0, 0\r\n    ];\r\n    auto bmpStream32 = new ArrayStream(bmpData32);\r\n    img = loadBMP(bmpStream32);\r\n    assert(img[2,2].convert(8) == Color4(208, 94, 34, 255));\r\n    assert(img[5,2].convert(8) == Color4(134, 178, 112, 255));\r\n    assert(img[2,5].convert(8) == Color4(34, 143, 208, 255));\r\n    assert(img[5,5].convert(8) == Color4(228, 168, 52, 255));\r\n\r\n    //32 bit with transparency\r\n    ubyte[] bmpData32_alpha =\r\n    [\r\n        66, 77, 122, 1, 0, 0, 0, 0, 0, 0, 122, 0, 0, 0, 108, 0, 0, 0, 8, 0, 0, 0, 8,\r\n        0, 0, 0, 1, 0, 32, 0, 3, 0, 0, 0, 0, 1, 0, 0, 109, 11, 0, 0, 109, 11, 0, 0, 0,\r\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 255, 1,\r\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,\r\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255,\r\n        249, 173, 0, 206, 142, 67, 52, 231, 194, 127, 176, 248, 229, 183, 239, 249,\r\n        229, 182, 240, 235, 196, 126, 179, 207, 143, 68, 55, 255, 255, 238, 0, 168, 86,\r\n        17, 50, 198, 128, 57, 207, 230, 188, 116, 255, 200, 211, 179, 255, 131, 192,\r\n        209, 255, 164, 172, 153, 255, 199, 131, 60, 211, 171, 88, 18, 55, 162, 88, 23,\r\n        171, 171, 103, 22, 255, 203, 147, 48, 255, 165, 188, 159, 255, 78, 202, 251,\r\n        255, 71, 168, 211, 255, 174, 119, 55, 255, 166, 89, 21, 179, 190, 142, 100,\r\n        233, 191, 137, 58, 255, 215, 167, 81, 255, 216, 198, 158, 255, 162, 198, 181,\r\n        255, 106, 177, 169, 255, 171, 153, 111, 255, 193, 142, 98, 239, 198, 155, 120,\r\n        232, 184, 154, 130, 255, 140, 155, 166, 255, 149, 194, 197, 255, 153, 206, 185,\r\n        255, 84, 180, 141, 255, 129, 151, 106, 255, 198, 154, 121, 238, 196, 152, 117,\r\n        168, 191, 166, 157, 255, 59, 109, 202, 255, 51, 140, 222, 255, 127, 188, 190,\r\n        255, 119, 179, 141, 255, 129, 146, 106, 255, 188, 149, 117, 175, 193, 147, 111,\r\n        47, 213, 186, 167, 203, 164, 163, 201, 255, 138, 156, 216, 255, 212, 216, 226,\r\n        255, 237, 225, 212, 255, 213, 189, 168, 207, 190, 148, 112, 52, 255, 255, 255,\r\n        0, 212, 179, 149, 47, 236, 218, 201, 169, 247, 237, 227, 233, 246, 237, 229,\r\n        234, 233, 217, 203, 172, 214, 182, 154, 50, 255, 255, 255, 0\r\n    ];\r\n    auto bmpStream32_alpha = new ArrayStream(bmpData32_alpha);\r\n    img = loadBMP(bmpStream32_alpha);\r\n    assert(img[1,1].convert(8) == Color4(167, 186, 213, 203));\r\n    assert(img[1,6].convert(8) == Color4(57, 128, 198, 207));\r\n    assert(img[2,2].convert(8) == Color4(202, 109, 59, 255));\r\n    assert(img[5,5].convert(8) == Color4(211, 168, 71, 255));\r\n\r\n    //24 bit\r\n    ubyte[] bmpData24 =\r\n    [\r\n        66, 77, 248, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, 0, 8, 0, 0, 0, 8, 0,\r\n        0, 0, 1, 0, 24, 0, 0, 0, 0, 0, 194, 0, 0, 0, 18, 11, 0, 0, 18, 11, 0, 0, 0, 0,\r\n        0, 0, 0, 0, 0, 0, 255, 255, 255, 245, 235, 224, 229, 199, 154, 248, 227, 185,\r\n        255, 229, 181, 236, 203, 152, 244, 234, 223, 255, 255, 255, 244, 234, 224, 202,\r\n        139, 76, 242, 199, 126, 202, 217, 187, 117, 190, 218, 167, 177, 160, 209, 140,\r\n        72, 243, 231, 221, 196, 149, 107, 166, 97, 16, 208, 143, 34, 161, 188, 160, 59,\r\n        207, 255, 52, 168, 228, 182, 115, 42, 196, 144, 97, 196, 151, 116, 192, 136,\r\n        51, 226, 169, 71, 231, 202, 160, 170, 199, 178, 101, 178, 172, 176, 156, 116,\r\n        201, 153, 112, 204, 162, 127, 185, 156, 134, 136, 155, 170, 153, 201, 201, 161,\r\n        211, 186, 69, 179, 136, 123, 151, 103, 210, 164, 133, 215, 183, 153, 201, 174,\r\n        166, 34, 94, 208, 29, 132, 228, 125, 188, 190, 112, 178, 134, 120, 144, 104,\r\n        213, 181, 154, 246, 240, 233, 221, 193, 168, 167, 168, 213, 127, 147, 220, 220,\r\n        224, 236, 255, 239, 232, 220, 191, 169, 245, 238, 230, 255, 255, 255, 247, 240,\r\n        233, 235, 213, 186, 252, 237, 216, 245, 231, 217, 231, 212, 193, 246, 239, 230,\r\n        255, 255, 255, 0, 0\r\n    ];\r\n    auto bmpStream24 = new ArrayStream(bmpData24);\r\n    img = loadBMP(bmpStream24);\r\n    assert(img[2,2].convert(8) == Color4(208, 94, 34, 255));\r\n    assert(img[5,5].convert(8) == Color4(228, 168, 52, 255));\r\n\r\n    //16 bit X1 R5 G5 B5\r\n    ubyte[] bmpData16_1_5_5_5 =\r\n    [\r\n        66, 77, 184, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, 0, 8, 0, 0, 0, 8, 0,\r\n        0, 0, 1, 0, 16, 0, 0, 0, 0, 0, 130, 0, 0, 0, 18, 11, 0, 0, 18, 11, 0, 0, 0, 0,\r\n        0, 0, 0, 0, 0, 0, 255, 127, 190, 111, 28, 79, 158, 91, 159, 91, 61, 75, 158,\r\n        111, 255, 127, 158, 111, 57, 38, 29, 63, 89, 95, 238, 110, 212, 78, 57, 38,\r\n        158, 111, 88, 54, 148, 9, 57, 18, 244, 78, 39, 127, 134, 114, 214, 21, 88, 50,\r\n        88, 58, 55, 26, 187, 38, 60, 79, 21, 91, 204, 86, 117, 58, 120, 58, 153, 62,\r\n        118, 66, 113, 86, 19, 99, 84, 95, 200, 70, 79, 54, 154, 66, 218, 78, 184, 82,\r\n        100, 101, 4, 114, 239, 94, 206, 66, 79, 54, 218, 78, 190, 115, 251, 82, 148,\r\n        106, 79, 110, 123, 119, 191, 115, 251, 86, 190, 115, 255, 127, 190, 115, 93,\r\n        95, 191, 107, 158, 107, 92, 95, 190, 115, 255, 127, 0, 0\r\n    ];\r\n    auto bmpStream16_1_5_5_5 = new ArrayStream(bmpData16_1_5_5_5);\r\n    img = loadBMP(bmpStream16_1_5_5_5);\r\n\r\n    /*TODO: pixel comparisons\r\n     * GIMP shows slightly different pixel values on the same images.\r\n     */\r\n\r\n    //16 bit X4 R4 G4 B4\r\n    ubyte[] bmpData16_4_4_4_4 =\r\n    [\r\n        66, 77, 200, 0, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 56, 0, 0, 0, 8, 0, 0, 0, 8, 0,\r\n        0, 0, 1, 0, 16, 0, 3, 0, 0, 0, 130, 0, 0, 0, 18, 11, 0, 0, 18, 11, 0, 0, 0, 0,\r\n        0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 240, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 255, 15,\r\n        238, 13, 205, 9, 223, 11, 223, 11, 206, 9, 238, 13, 255, 15, 238, 13, 140, 4,\r\n        206, 7, 220, 11, 183, 13, 170, 9, 140, 4, 238, 13, 156, 6, 106, 1, 140, 2, 185,\r\n        9, 195, 15, 163, 13, 123, 2, 140, 6, 156, 7, 139, 3, 173, 4, 206, 9, 202, 10,\r\n        166, 10, 154, 7, 156, 7, 172, 7, 155, 8, 152, 10, 201, 12, 201, 11, 180, 8,\r\n        151, 6, 172, 8, 189, 9, 172, 10, 98, 12, 130, 13, 183, 11, 167, 8, 135, 6, 189,\r\n        9, 238, 14, 189, 10, 170, 13, 151, 13, 221, 14, 239, 14, 189, 10, 238, 14, 255,\r\n        15, 239, 14, 222, 11, 239, 13, 238, 13, 206, 11, 238, 14, 255, 15, 0, 0\r\n    ];\r\n    auto bmpStream16_4_4_4_4 = new ArrayStream(bmpData16_4_4_4_4);\r\n    img = loadBMP(bmpStream16_4_4_4_4);\r\n\r\n    /*TODO: pixel comparisons\r\n     * GIMP shows slightly different pixel values on the same images.\r\n     */\r\n\r\n    //16 bit R5 G6 B5\r\n    ubyte[] bmpData16_5_6_5 =\r\n    [\r\n        66, 77, 200, 0, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 56, 0, 0, 0, 8, 0, 0, 0, 8, 0,\r\n        0, 0, 1, 0, 16, 0, 3, 0, 0, 0, 130, 0, 0, 0, 18, 11, 0, 0, 18, 11, 0, 0, 0, 0,\r\n        0, 0, 0, 0, 0, 0, 0, 248, 0, 0, 224, 7, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 255,\r\n        255, 94, 223, 60, 158, 30, 183, 63, 183, 93, 150, 94, 223, 255, 255, 94, 223,\r\n        89, 76, 61, 126, 217, 190, 238, 221, 148, 157, 121, 76, 62, 223, 184, 108, 20,\r\n        19, 121, 36, 212, 157, 103, 254, 70, 229, 150, 43, 152, 100, 184, 116, 87, 52,\r\n        91, 77, 92, 158, 53, 182, 140, 173, 245, 116, 216, 116, 25, 125, 246, 132, 209,\r\n        172, 83, 198, 148, 190, 136, 141, 175, 108, 58, 133, 186, 157, 120, 165, 228,\r\n        202, 36, 228, 207, 189, 142, 133, 143, 108, 186, 157, 126, 231, 27, 166, 84,\r\n        213, 143, 220, 251, 238, 127, 231, 251, 173, 126, 231, 255, 255, 126, 231, 189,\r\n        190, 127, 215, 62, 215, 156, 190, 126, 231, 255, 255, 0, 0\r\n    ];\r\n    auto bmpStream16_5_6_5 = new ArrayStream(bmpData16_5_6_5);\r\n    img = loadBMP(bmpStream16_5_6_5);\r\n\r\n    /*TODO: pixel comparisons\r\n     * GIMP shows slightly different pixel values on the same images.\r\n     */\r\n\r\n    //4 bit\r\n    ubyte[] bmpData4 =\r\n    [\r\n        66, 77, 150, 0, 0, 0, 0, 0, 0, 0, 118, 0, 0, 0, 40, 0, 0, 0, 8, 0, 0, 0, 8, 0,\r\n        0, 0, 1, 0, 4, 0, 0, 0, 0, 0, 32, 0, 0, 0, 196, 14, 0, 0, 196, 14, 0, 0, 0, 0,\r\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 128, 0, 0, 0, 128, 128, 0, 128,\r\n        0, 0, 0, 128, 0, 128, 0, 128, 128, 0, 0, 128, 128, 128, 0, 192, 192, 192, 0, 0,\r\n        0, 255, 0, 0, 255, 0, 0, 0, 255, 255, 0, 255, 0, 0, 0, 255, 0, 255, 0, 255,\r\n        255, 0, 0, 255, 255, 255, 0, 255, 136, 136, 255, 246, 136, 136, 111, 116, 103,\r\n        187, 103, 118, 104, 131, 119, 119, 120, 131, 119, 136, 147, 135, 40, 248, 135,\r\n        255, 143, 255, 255, 255, 255\r\n    ];\r\n    auto bmpStream4 = new ArrayStream(bmpData4);\r\n    img = loadBMP(bmpStream4);\r\n    assert(img[2,2].convert(8) == Color4(255,0,0,255));\r\n    assert(img[1,1].convert(8) == Color4(192,192,192,255));\r\n    assert(img[6,2].convert(8) == Color4(0,128,0,255));\r\n}\r\n\r\n/**\r\n * Save BMP to file using local FileSystem.\r\n * Causes GC allocation\r\n */\r\nvoid saveBMP(SuperImage img, string filename)\r\n{\r\n    OutputStream output = openForOutput(filename);\r\n    Compound!(bool, string) res =\r\n        saveBMP(img, output);\r\n    output.close();\r\n\r\n    if (!res[0])\r\n        throw new BMPLoadException(res[1]);\r\n}\r\n\r\n/**\r\n * Save BMP to stream.\r\n * GC-free\r\n */\r\nCompound!(bool, string) saveBMP(SuperImage img, OutputStream output)\r\n{\r\n    Compound!(bool, string) error(string errorMsg)\r\n    {\r\n        return compound(false, errorMsg);\r\n    }\r\n\r\n    uint bytesPerRow = (img.width * 24 + 31) / 32 * 4;\r\n    uint dataOffset = 12 + BMPInfoSize.WIN;\r\n    uint fileSize = dataOffset + img.height * bytesPerRow;\r\n\r\n    output.writeArray(BMPMagic);\r\n    output.writeLE(fileSize);\r\n    output.writeLE(cast(ushort)0);\r\n    output.writeLE(cast(ushort)0);\r\n    output.writeLE(dataOffset);\r\n\r\n    output.writeLE(BMPInfoSize.WIN);\r\n    output.writeLE(img.width);\r\n    output.writeLE(img.height);\r\n    output.writeLE(cast(ushort)1);\r\n    output.writeLE(cast(ushort)24);\r\n    output.writeLE(BMPCompressionType.RGB);\r\n    output.writeLE(bytesPerRow * img.height);\r\n    output.writeLE(2834);\r\n    output.writeLE(2834);\r\n    output.writeLE(0);\r\n    output.writeLE(0);\r\n\r\n    foreach_reverse(y; 0..img.height)\r\n    {\r\n        foreach(x; 0..img.width)\r\n        {\r\n            ubyte[3] rgb;\r\n            ColorRGBA color = img[x, y].convert(8);\r\n            rgb[0] = cast(ubyte)color[2];\r\n            rgb[1] = cast(ubyte)color[1];\r\n            rgb[2] = cast(ubyte)color[0];\r\n            output.writeArray(rgb);\r\n        }\r\n        \r\n        //padding\r\n        for(uint i=0; i<(bytesPerRow-img.width*3); ++i)\r\n        {\r\n            output.writeLE(cast(ubyte)0);\r\n        }\r\n    }\r\n\r\n    return compound(true, \"\");\r\n}\r\n"
  },
  {
    "path": "dlib/image/io/hdr.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Decode and encode Radiance HDR/RGBE images\r\n *\r\n * Copyright: Timur Gafarov 2016-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.io.hdr;\r\n\r\nimport std.stdio;\r\nimport std.math;\r\nimport dlib.core.memory;\r\nimport dlib.core.stream;\r\nimport dlib.core.compound;\r\nimport dlib.container.array;\r\nimport dlib.filesystem.local;\r\nimport dlib.image.color;\r\nimport dlib.image.image;\r\nimport dlib.image.hdri;\r\nimport dlib.image.io;\nimport dlib.math.utils;\r\n\nstruct ColorRGBE\n{\n    ubyte r;\n    ubyte g;\n    ubyte b;\n    ubyte e;\n}\n\nColorRGBE floatToRGBE(Color4f c)\n{\n    ColorRGBE rgbe;\n\n    float v = c.r;\n    if (c.g > v)\n        v = c.g;\n    if (c.b > v)\n        v = c.b;\n    if (v < EPSILON)\n    {\n        rgbe.r = 0;\n        rgbe.g = 0;\n        rgbe.b = 0;\n        rgbe.e = 0;\n    }\n    else\n    {\n        int e;\n        v = frexp(v, e) * 256.0f / v;\n        rgbe.r = cast(ubyte)(c.r * v);\n        rgbe.g = cast(ubyte)(c.g * v);\n        rgbe.b = cast(ubyte)(c.b * v);\n        rgbe.e = cast(ubyte)(e + 128);\n    }\n\n    return rgbe;\n}\n\nvoid readLineFromStream(InputStream istrm, ref Array!char line)\r\n{\r\n    char c;\r\n    do\r\n    {\r\n        if (istrm.readable)\r\n            istrm.readBytes(&c, 1);\r\n        else\r\n            break;\r\n\r\n        if (c != '\\n')\r\n            line.append(c);\r\n    }\r\n    while(c != '\\n');\r\n}\r\n\r\nclass HDRLoadException: ImageLoadException\r\n{\r\n    this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)\r\n    {\r\n        super(msg, file, line, next);\r\n    }\r\n}\r\n\r\n/**\r\n * Load HDR from file using local FileSystem.\r\n * Causes GC allocation\r\n */\r\nSuperHDRImage loadHDR(string filename)\r\n{\r\n    InputStream input = openForInput(filename);\r\n    ubyte[] data = New!(ubyte[])(cast(size_t)input.size);\r\n    input.fillArray(data);\r\n    ArrayStream arrStrm = New!ArrayStream(data);\r\n    auto img = loadHDR(arrStrm);\r\n    Delete(arrStrm);\r\n    Delete(data);\r\n    input.close();\r\n    return img;\r\n}\r\n\r\n/**\r\n * Load HDR from stream using default image factory.\r\n * Causes GC allocation\r\n */\r\nSuperHDRImage loadHDR(InputStream istrm)\r\n{\r\n    Compound!(SuperHDRImage, string) res =\r\n        loadHDR(istrm, defaultHDRImageFactory);\r\n    if (res[0] is null)\r\n        throw new HDRLoadException(res[1]);\r\n    else\r\n        return res[0];\r\n}\r\n\r\n/**\r\n * Load HDR from stream using specified image factory.\r\n * GC-free\r\n */\r\nCompound!(SuperHDRImage, string) loadHDR(\r\n    InputStream istrm,\r\n    SuperHDRImageFactory imgFac)\r\n{\r\n    SuperHDRImage img = null;\r\n\r\n    Compound!(SuperHDRImage, string) error(string errorMsg)\r\n    {\r\n        if (img)\r\n        {\r\n            img.free();\r\n            img = null;\r\n        }\r\n        return compound(img, errorMsg);\r\n    }\r\n\r\n    char[11] magic;\r\n    istrm.fillArray(magic);\n    if (magic != \"#?RADIANCE\\n\")\r\n    {\r\n        if (magic[0..7] == \"#?RGBE\\n\")\r\n        {\r\n            istrm.position = 7;\r\n        }\n        else\n            return error(\"loadHDR error: signature check failed\");\r\n    }\r\n\r\n    // Read header\r\n    Array!char line;\r\n    do\r\n    {\r\n        line.free();\r\n        readLineFromStream(istrm, line);\r\n        // TODO: parse assignments\r\n    }\r\n    while (line.length);\r\n\r\n    // Read resolution line\r\n    line.free();\r\n    readLineFromStream(istrm, line);\r\n\r\n    char xsign, ysign;\r\n    uint width, height;\r\n    int count = sscanf(line.data.ptr, \"%cY %u %cX %u\", &ysign, &height, &xsign, &width);\r\n    if (count != 4)\r\n        return error(\"loadHDR error: invalid resolution line\");\r\n\r\n    // Read pixel data\r\n    ubyte[] dataRGBE = New!(ubyte[])(width * height * 4);\r\n    ubyte[4] col;\r\n    for (uint y = 0; y < height; y++)\r\n    {\r\n        istrm.readBytes(col.ptr, 4);\r\n        //Header of 0x2, 0x2 is new Radiance RLE scheme\r\n        if (col[0] == 2 && col[1] == 2 && col[2] >= 0)\r\n        {\r\n            // Each channel is run length encoded seperately\r\n            for (uint chi = 0; chi < 4; chi++)\r\n            {\r\n                uint x = 0;\r\n                while (x < width)\r\n                {\r\n                    uint start = (y * width + x) * 4;\r\n                    ubyte num = 0;\r\n                    istrm.readBytes(&num, 1);\r\n                    if (num <= 128) // No run, just read the values\r\n                    {\r\n                        for (uint i = 0; i < num; i++)\r\n                        {\r\n                            ubyte value;\r\n                            istrm.readBytes(&value, 1);\r\n                            dataRGBE[start + chi + i*4] = value;\r\n                        }\r\n                    }\r\n                    else // We have a run, so get the value and set all the values for this run\r\n                    {\r\n                        ubyte value;\r\n                        istrm.readBytes(&value, 1);\r\n                        num -= 128;\r\n                        for (uint i = 0; i < num; i++)\r\n                        {\r\n                            dataRGBE[start + chi + i*4] = value;\r\n                        }\r\n                    }\r\n\r\n                    x += num;\r\n                }\r\n            }\r\n        }\r\n        else // Old Radiance RLE scheme\r\n        {\r\n            for (uint x = 0; x < width; x++)\r\n            {\r\n                if (x > 0)\r\n                    istrm.readBytes(col.ptr, 4);\r\n\r\n                uint prev = (y * width + x - 1) * 4;\r\n                uint start = (y * width + x) * 4;\r\n\r\n                // Check for the RLE header for this scanline\r\n                if (col[0] == 1 && col[1] == 1 && col[2] == 1)\r\n                {\r\n                    // Do the run\r\n                    int num = (cast(int)col[3]) & 0xFF;\r\n\r\n                    ubyte r = dataRGBE[prev];\r\n                    ubyte g = dataRGBE[prev + 1];\r\n                    ubyte b = dataRGBE[prev + 2];\r\n                    ubyte e = dataRGBE[prev + 3];\r\n\r\n                    for (uint i = 0; i < num; i++)\r\n                    {\r\n                        dataRGBE[start + i*4 + 0] = r;\r\n                        dataRGBE[start + i*4 + 1] = g;\r\n                        dataRGBE[start + i*4 + 2] = b;\r\n                        dataRGBE[start + i*4 + 3] = e;\r\n                    }\r\n\r\n                    x += num-1;\r\n                }\r\n                else // No runs here, just read the data\r\n                {\r\n                    dataRGBE[start] = col[0];\r\n                    dataRGBE[start + 1] = col[1];\r\n                    dataRGBE[start + 2] = col[2];\r\n                    dataRGBE[start + 3] = col[3];\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    // Convert RGBE to IEEE floats\r\n    img = imgFac.createImage(width, height);\r\n    foreach(y; 0..height)\r\n    foreach(x; 0..width)\r\n    {\r\n        size_t start = (width * y + x) * 4;\r\n        ubyte exponent = dataRGBE[start + 3];\r\n        if (exponent == 0)\r\n        {\r\n            img[x, y] = Color4f(0, 0, 0, 1);\r\n        }\r\n        else\r\n        {\r\n            float v = ldexp(1.0, cast(int)exponent - (128 + 8));\r\n            float r = cast(float)(dataRGBE[start]) * v;\r\n            float g = cast(float)(dataRGBE[start + 1]) * v;\r\n            float b = cast(float)(dataRGBE[start + 2]) * v;\r\n            img[x, y] = Color4f(r, g, b, 1);\r\n        }\r\n    }\n\n    Delete(dataRGBE);\r\n\r\n    return compound(img, \"\");\r\n}\n\n/**\r\n * Save HDR to file using local FileSystem.\r\n * Causes GC allocation\r\n */\r\nvoid saveHDR(SuperHDRImage img, string filename)\r\n{\r\n    OutputStream output = openForOutput(filename);\r\n    Compound!(bool, string) res = saveHDR(img, output);\r\n    output.close();\r\n}\n\n/**\r\n * Save HDR to stream.\r\n * GC-free\r\n */\nCompound!(bool, string) saveHDR(SuperHDRImage img, OutputStream output)\r\n{\n    Compound!(bool, string) error(string errorMsg)\r\n    {\r\n        return compound(false, errorMsg);\r\n    }\n\n    // Signature and header\n    string hdrStart = \"#?RADIANCE\\n\\n\"; // double LF needed to mark end of header\n    output.writeArray(hdrStart);\n\n    // Resolution line\n    char[256] resolution;\n    int len = sprintf(resolution.ptr, \"-Y %d +X %d\\n\", img.height, img.width);\n    output.writeArray(resolution[0..len]);\n\n    ubyte[] scanline = New!(ubyte[])(img.width * 4);\n\n    for (uint y = 0; y < img.height; y++)\r\n    {\n        ubyte[4] scanlineHeader;\n        scanlineHeader[0] = 2;\n        scanlineHeader[1] = 2;\n        scanlineHeader[2] = cast(ubyte)(img.width >> 8);\n        scanlineHeader[3] = cast(ubyte)(img.width & 0xFF);\r\n        output.writeArray(scanlineHeader);\n\n        // Convert a scanline to RGBE decomposing channels\n        for (uint x = 0; x < img.width; x++)\n        {\n            ColorRGBE rgbe = img[x, y].floatToRGBE;\n            scanline[x] = rgbe.r;\n            scanline[x + img.width] = rgbe.g;\n            scanline[x + img.width * 2] = rgbe.b;\n            scanline[x + img.width * 3] = rgbe.e;\n        }\n\n        // Write channels\n        foreach(ch; 0..4)\n        {\n            uint offset = ch * img.width;\n            writeBufferRLE(output, scanline[offset..offset+img.width]);\n        }\n    }\n\n    Delete(scanline);\n\n    return compound(true, \"\");\n}\n\n/*\n * Based on code by Bruce Walter:\n * http://www.graphics.cornell.edu/~bjw/rgbe/rgbe.c\n */\nvoid writeBufferRLE(OutputStream output, ubyte[] data)\n{\n    enum MINRUNLENGTH = 4;\n    int cur, beg_run, run_count, old_run_count, nonrun_count;\n    ubyte[2] buf;\n\n    cur = 0;\n    while(cur < data.length)\n    {\n        beg_run = cur;\n\n        // find next run of length at least 4 if one exists\n        run_count = old_run_count = 0;\n        while((run_count < MINRUNLENGTH) && (beg_run < data.length))\n        {\n            beg_run += run_count;\n            old_run_count = run_count;\n            run_count = 1;\n            while((beg_run + run_count < data.length) && (run_count < 127)\n                && (data[beg_run] == data[beg_run + run_count]))\n                run_count++;\n        }\n\n        // if data before next big run is a short run then write it as such\n        if ((old_run_count > 1) && (old_run_count == beg_run - cur))\n        {\n            buf[0] = cast(ubyte)(128 + old_run_count); // write short run\n            buf[1] = data[cur];\n            output.writeArray(buf);\n            cur = beg_run;\n        }\n\n        // write out bytes until we reach the start of the next run\n        while(cur < beg_run)\n        {\n            nonrun_count = beg_run - cur;\n            if (nonrun_count > 128)\n                nonrun_count = 128;\n            buf[0] = cast(ubyte)nonrun_count;\n            output.writeBytes(buf.ptr, 1);\n            output.writeBytes(&data[cur], nonrun_count);\n            cur += nonrun_count;\n        }\n\n        // write out next run if one was found\n        if (run_count >= MINRUNLENGTH)\n        {\n            buf[0] = cast(ubyte)(128 + run_count);\n            buf[1] = data[beg_run];\n            output.writeArray(buf);\n            cur += run_count;\n        }\n    }\n}\n"
  },
  {
    "path": "dlib/image/io/jpeg.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Decode JPEG images\r\n *\r\n * Description:\r\n * Simple self-contained JPEG decoder, supports only baseline format.\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.io.jpeg;\r\n\r\nimport std.stdio;\r\nimport std.algorithm;\r\nimport std.string;\r\nimport std.traits;\r\nimport std.math;\r\nimport dlib.core.memory;\r\nimport dlib.core.stream;\r\nimport dlib.core.compound;\r\nimport dlib.core.bitio;\r\nimport dlib.container.array;\r\nimport dlib.filesystem.local;\r\nimport dlib.image.color;\r\nimport dlib.image.image;\r\nimport dlib.math.utils;\r\n\r\n// Uncomment this to see debug messages\r\n//version = JPEGDebug;\r\n\r\nT readNumeric(T) (InputStream istrm, Endian endian = Endian.Little)\r\nif (is(T == ubyte))\r\n{\r\n    ubyte b;\r\n    istrm.readBytes(&b, 1);\r\n    return b;\r\n}\r\n\r\nT readNumeric(T) (InputStream istrm, Endian endian = Endian.Little)\r\nif (is(T == ushort))\r\n{\r\n    union U16\r\n    {\r\n        ubyte[2] asBytes;\r\n        ushort asUshort;\r\n    }\r\n    U16 u16;\r\n    istrm.readBytes(u16.asBytes.ptr, 2);\r\n    version(LittleEndian)\r\n    {\r\n        if (endian == Endian.Big)\r\n            return u16.asUshort.swapEndian16;\r\n        else\r\n            return u16.asUshort;\r\n    }\r\n    else\r\n    {\r\n        if (endian == Endian.Little)\r\n            return u16.asUshort.swapEndian16;\r\n        else\r\n            return u16.asUshort;\r\n    }\r\n}\r\n\r\nchar[size] readChars(size_t size) (InputStream istrm)\r\n{\r\n    char[size] chars;\r\n    istrm.readBytes(chars.ptr, size);\r\n    return chars;\r\n}\r\n\r\n/*\r\n * JPEG-related Huffman coding\r\n */\r\n\r\nstruct HuffmanCode\r\n{\r\n    ushort bits;\r\n    ushort length;\r\n\r\n    auto bitString()\r\n    {\r\n        return .bitString(bits, length);\r\n    }\r\n}\r\n\r\nstruct HuffmanTableEntry\r\n{\r\n    HuffmanCode code;\r\n    ubyte value;\r\n}\r\n\r\nArray!char bitString(T)(T n, uint len = 1) if (isIntegral!T)\r\n{\r\n    Array!char arr;\r\n\r\n    const int size = T.sizeof * 8;\r\n\r\n    bool s = 0;\r\n    for (int a = 0; a < size; a++)\r\n    {\r\n        bool bit = n >> (size - 1);\r\n        if (bit)\r\n            s = 1;\r\n        if (s)\r\n        {\r\n            arr.append(bit + '0');\r\n        }\r\n        n <<= 1;\r\n    }\r\n\r\n    while (arr.length < len)\r\n        arr.appendLeft('0');\r\n\r\n    return arr;\r\n}\r\n\r\nstruct HuffmanTreeNode\r\n{\r\n    HuffmanTreeNode* parent;\r\n    HuffmanTreeNode* left;\r\n    HuffmanTreeNode* right;\r\n    ubyte ch;\r\n    uint freq;\r\n    bool blank = true;\r\n\r\n    this(\r\n        HuffmanTreeNode* leftNode,\r\n        HuffmanTreeNode* rightNode,\r\n        ubyte symbol,\r\n        uint frequency,\r\n        bool isBlank)\r\n    {\r\n        parent = null;\r\n        left = leftNode;\r\n        right = rightNode;\r\n\r\n        if (left !is null)\r\n            left.parent = &this;\r\n        if (right !is null)\r\n            right.parent = &this;\r\n\r\n        ch = symbol;\r\n        freq = frequency;\r\n        blank = isBlank;\r\n    }\r\n\r\n    bool isLeaf()\r\n    {\r\n        return (left is null && right is null);\r\n    }\r\n\r\n    void free()\r\n    {\r\n        if (left !is null)\r\n        {\r\n            left.free();\r\n            Delete(left);\r\n        }\r\n\r\n        if (right !is null)\r\n        {\r\n            right.free();\r\n            Delete(right);\r\n        }\r\n    }\r\n}\r\n\r\nHuffmanTreeNode* emptyNode()\r\n{\r\n    return New!HuffmanTreeNode(null, null, cast(ubyte)0, 0, false);\r\n}\r\n\r\nHuffmanTreeNode* treeFromTable(Array!(HuffmanTableEntry) table)\r\n{\r\n    HuffmanTreeNode* root = emptyNode();\r\n\r\n    foreach(i, v; table.data)\r\n        treeAddCode(root, v.code, v.value);\r\n\r\n    return root;\r\n}\r\n\r\nvoid treeAddCode(HuffmanTreeNode* root, HuffmanCode code, ubyte value)\r\n{\r\n    HuffmanTreeNode* node = root;\r\n    auto bs = code.bitString;\r\n    foreach(bit; bs.data)\r\n    {\r\n        if (bit == '0')\r\n        {\r\n            if (node.left is null)\r\n            {\r\n                node.left = emptyNode();\r\n                node.left.parent = node;\r\n            }\r\n\r\n            node = node.left;\r\n        }\r\n        else if (bit == '1')\r\n        {\r\n            if (node.right is null)\r\n            {\r\n                node.right = emptyNode();\r\n                node.right.parent = node;\r\n            }\r\n\r\n            node = node.right;\r\n        }\r\n    }\r\n    assert (node !is null);\r\n    node.ch = value;\r\n    bs.free();\r\n}\r\n\r\n/*\r\n * JPEG-related data types\r\n */\r\n\r\nenum JPEGMarkerType\r\n{\r\n    Unknown,\r\n    SOI,\r\n    SOF0,\r\n    SOF1,\r\n    SOF2,\r\n    DHT,\r\n    DQT,\r\n    DRI,\r\n    SOS,\r\n    RSTn,\r\n    APP0,\r\n    APPn,\r\n    COM,\r\n    EOI\r\n}\r\n\r\nstruct JPEGImage\r\n{\r\n    struct JFIF\r\n    {\r\n        ubyte versionMajor;\r\n        ubyte versionMinor;\r\n        ubyte units;\r\n        ushort xDensity;\r\n        ushort yDensity;\r\n        ubyte thumbnailWidth;\r\n        ubyte thumbnailHeight;\r\n        ubyte[] thumbnail;\r\n\r\n        void free()\r\n        {\r\n            if (thumbnail.length)\r\n                Delete(thumbnail);\r\n        }\r\n    }\r\n\r\n    struct DQT\r\n    {\r\n        ubyte precision;\r\n        ubyte tableId;\r\n        ubyte[] table;\r\n\r\n        void free()\r\n        {\r\n            if (table.length)\r\n                Delete(table);\r\n        }\r\n    }\r\n\r\n    struct FrameComponent\r\n    {\r\n        ubyte hSubsampling;\r\n        ubyte vSubsampling;\r\n        ubyte dqtTableId;\r\n    }\r\n\r\n    struct Frame\r\n    {\r\n        ubyte precision;\r\n        ushort height;\r\n        ushort width;\r\n        ubyte componentsNum;\r\n        FrameComponent[] components;\r\n        bool isProgressive;\r\n\r\n        void free()\r\n        {\r\n            if (components.length)\r\n                Delete(components);\r\n        }\r\n    }\r\n\r\n    struct DHT\r\n    {\r\n        ubyte clas;\r\n        ubyte tableId;\r\n        Array!HuffmanTableEntry huffmanTable;\r\n        HuffmanTreeNode* huffmanTree;\r\n\r\n        void free()\r\n        {\r\n            huffmanTree.free();\r\n            Delete(huffmanTree);\r\n            huffmanTable.free();\r\n        }\r\n    }\r\n\r\n    struct ScanComponent\r\n    {\r\n        ubyte tableIdDC;\r\n        ubyte tableIdAC;\r\n    }\r\n\r\n    struct Scan\r\n    {\r\n        ubyte componentsNum;\r\n        ScanComponent[] components;\r\n        ubyte spectralSelectionStart;\r\n        ubyte spectralSelectionEnd;\r\n        ubyte successiveApproximationBitHigh;\r\n        ubyte successiveApproximationBitLow;\r\n\r\n        void free()\r\n        {\r\n            if (components.length)\r\n                Delete(components);\r\n        }\r\n    }\r\n\r\n    JFIF jfif;\r\n    DQT[] dqt;\r\n    Frame frame;\r\n    DHT[] dht;\r\n    // TODO: multiple scans for progressive JPEG support\r\n    Scan scan;\r\n\r\n    DQT* addDQT()\r\n    {\r\n        if (dqt.length > 0)\r\n            reallocateArray(dqt, dqt.length+1);\r\n        else\r\n            dqt = New!(DQT[])(1);\r\n        return &dqt[$-1];\r\n    }\r\n\r\n    DHT* addDHT()\r\n    {\r\n        if (dht.length > 0)\r\n            reallocateArray(dht, dht.length+1);\r\n        else\r\n            dht = New!(DHT[])(1);\r\n        return &dht[$-1];\r\n    }\r\n\r\n    void free()\r\n    {\r\n        jfif.free();\r\n        foreach(ref t; dqt) t.free();\r\n        if (dqt.length)\r\n            Delete(dqt);\r\n        frame.free();\r\n        foreach(ref t; dht) t.free();\r\n        if (dht.length)\r\n            Delete(dht);\r\n        scan.free();\r\n    }\r\n\r\n    DQT* getQuantizationTable(ubyte id)\r\n    {\r\n        foreach(ref t; dqt)\r\n            if (t.tableId == id)\r\n                return &t;\r\n        return null;\r\n    }\r\n\r\n    DHT* getHuffmanTable(ubyte clas, ubyte id)\r\n    {\r\n        foreach(ref t; dht)\r\n            if (t.clas == clas &&\r\n                t.tableId == id)\r\n                return &t;\r\n        return null;\r\n    }\r\n}\r\n\r\n/**\r\n * Load JPEG from file using local FileSystem.\r\n * Causes GC allocation\r\n */\r\nSuperImage loadJPEG(string filename)\r\n{\r\n    InputStream input = openForInput(filename);\r\n    ubyte[] data = New!(ubyte[])(cast(size_t)input.size);\r\n    input.fillArray(data);\r\n    ArrayStream arrStrm = New!ArrayStream(data);\r\n    auto img = loadJPEG(arrStrm);\r\n    Delete(arrStrm);\r\n    Delete(data);\r\n    input.close();\r\n    return img;\r\n}\r\n\r\n/**\r\n * Load JPEG from stream using default image factory.\r\n * Causes GC allocation\r\n */\r\nSuperImage loadJPEG(InputStream istrm)\r\n{\r\n    Compound!(SuperImage, string) res =\r\n        loadJPEG(istrm, defaultImageFactory);\r\n    if (res[0] is null)\r\n        throw new Exception(res[1]);\r\n    else\r\n        return res[0];\r\n}\r\n\r\n/**\r\n * Load JPEG from stream using specified image factory.\r\n * GC-free\r\n */\r\nCompound!(SuperImage, string) loadJPEG(\r\n    InputStream istrm,\r\n    SuperImageFactory imgFac)\r\n{\r\n    JPEGImage jpg;\r\n    SuperImage img = null;\r\n\r\n    while (istrm.readable)\r\n    {\r\n        JPEGMarkerType mt;\r\n        auto res = readMarker(&jpg, istrm, &mt);\r\n        if (res[0])\r\n        {\r\n            // TODO: add progressive JPEG support\r\n            if (mt == JPEGMarkerType.SOF2)\r\n            {\r\n                jpg.free();\r\n                return compound(img, \"loadJPEG error: progressive JPEG is not supported\");\r\n            }\r\n            else if (mt == JPEGMarkerType.SOS)\r\n                break;\r\n        }\r\n        else\r\n        {\r\n            jpg.free();\r\n            return compound(img, res[1]);\r\n        }\r\n    }\r\n    auto res = decodeScanData(&jpg, istrm, imgFac);\r\n    jpg.free();\r\n    return res;\r\n}\r\n\r\n/*\r\n *  Decode marker from JPEG stream\r\n */\r\nCompound!(bool, string) readMarker(\r\n    JPEGImage* jpg,\r\n    InputStream istrm,\r\n    JPEGMarkerType* mt)\r\n{\r\n    ushort magic = istrm.readNumeric!ushort(Endian.Big);\r\n\r\n    switch (magic)\r\n    {\r\n        case 0xFFD8:\r\n            // Start of image\r\n            *mt = JPEGMarkerType.SOI;\r\n            version(JPEGDebug) writeln(\"SOI\");\r\n            break;\r\n\r\n        case 0xFFE0:\r\n            // JFIF data\r\n            *mt = JPEGMarkerType.APP0;\r\n            return readJFIF(jpg, istrm);\r\n\r\n        case 0xFFE1:\r\n            // Application-specific data\r\n            *mt = JPEGMarkerType.APPn;\r\n            return readAPPn(jpg, istrm, 1);\r\n\r\n        case 0xFFE2:\r\n            // Application-specific data\r\n            *mt = JPEGMarkerType.APPn;\r\n            return readAPPn(jpg, istrm, 2);\r\n\r\n        case 0xFFE3:\r\n            // Application-specific data\r\n            *mt = JPEGMarkerType.APPn;\r\n            return readAPPn(jpg, istrm, 3);\r\n\r\n        case 0xFFE4:\r\n            // Application-specific data\r\n            *mt = JPEGMarkerType.APPn;\r\n            return readAPPn(jpg, istrm, 4);\r\n\r\n        case 0xFFE5:\r\n            // Application-specific data\r\n            *mt = JPEGMarkerType.APPn;\r\n            return readAPPn(jpg, istrm, 5);\r\n\r\n        case 0xFFE6:\r\n            // Application-specific data\r\n            *mt = JPEGMarkerType.APPn;\r\n            return readAPPn(jpg, istrm, 6);\r\n\r\n        case 0xFFE7:\r\n            // Application-specific data\r\n            *mt = JPEGMarkerType.APPn;\r\n            return readAPPn(jpg, istrm, 7);\r\n\r\n        case 0xFFE8:\r\n            // Application-specific data\r\n            *mt = JPEGMarkerType.APPn;\r\n            return readAPPn(jpg, istrm, 8);\r\n\r\n        case 0xFFE9:\r\n            // Application-specific data\r\n            *mt = JPEGMarkerType.APPn;\r\n            return readAPPn(jpg, istrm, 9);\r\n\r\n        case 0xFFEA:\r\n            // Application-specific data\r\n            *mt = JPEGMarkerType.APPn;\r\n            return readAPPn(jpg, istrm, 10);\r\n\r\n        case 0xFFEB:\r\n            // Application-specific data\r\n            *mt = JPEGMarkerType.APPn;\r\n            return readAPPn(jpg, istrm, 11);\r\n\r\n        case 0xFFEC:\r\n            // Application-specific data\r\n            *mt = JPEGMarkerType.APPn;\r\n            return readAPPn(jpg, istrm, 12);\r\n\r\n        case 0xFFED:\r\n            // Application-specific data\r\n            *mt = JPEGMarkerType.APPn;\r\n            return readAPPn(jpg, istrm, 13);\r\n\r\n        case 0xFFEE:\r\n            // Application-specific data\r\n            *mt = JPEGMarkerType.APPn;\r\n            return readAPPn(jpg, istrm, 14);\r\n\r\n        case 0xFFEF:\r\n            // Application-specific data\r\n            *mt = JPEGMarkerType.APPn;\r\n            return readAPPn(jpg, istrm, 15);\r\n\r\n        case 0xFFDB:\r\n            // Quantization table(s)\r\n            *mt = JPEGMarkerType.DQT;\r\n            return readDQT(jpg, istrm);\r\n\r\n        case 0xFFC0:\r\n            // Start of frame (baseline)\r\n            *mt = JPEGMarkerType.SOF0;\r\n            return readSOF0(jpg, istrm);\r\n        \r\n        case 0xFFC1:\r\n            // Start of frame (extended sequential)\r\n            *mt = JPEGMarkerType.SOF1;\r\n            version(JPEGDebug) writeln(\"SOF1\");\r\n            break;\r\n\r\n        case 0xFFC2:\r\n            // Start of frame (progressive)\r\n            *mt = JPEGMarkerType.SOF2;\r\n            return readSOF2(jpg, istrm);\r\n\r\n        case 0xFFC4:\r\n            // Huffman table(s)\r\n            *mt = JPEGMarkerType.DHT;\r\n            return readDHT(jpg, istrm);\r\n\r\n        case 0xFFDA:\r\n            // Start of scan\r\n            *mt = JPEGMarkerType.SOS;\r\n            return readSOS(jpg, istrm);\r\n\r\n        case 0xFFFE:\r\n            // Comments\r\n            *mt = JPEGMarkerType.COM;\r\n            return readCOM(jpg, istrm);\r\n        \r\n        case 0xFFDD:\r\n            // Restart interval\r\n            // TODO\r\n            *mt = JPEGMarkerType.DRI;\r\n            version(JPEGDebug) writeln(\"DRI\");\r\n            break;\r\n        \r\n        case 0xFFD0:\r\n            // Restart\r\n            // TODO\r\n            *mt = JPEGMarkerType.RSTn;\n            version(JPEGDebug) writeln(\"RST0\");\r\n            break;\r\n        \r\n        case 0xFFD1:\r\n            // Restart\r\n            // TODO\r\n            *mt = JPEGMarkerType.RSTn;\r\n            version(JPEGDebug) writeln(\"RST1\");\r\n            break;\r\n        \r\n        case 0xFFD2:\r\n            // Restart\r\n            // TODO\r\n            *mt = JPEGMarkerType.RSTn;\r\n            version(JPEGDebug) writeln(\"RST2\");\r\n            break;\r\n        \r\n        case 0xFFD3:\r\n            // Restart\r\n            // TODO\r\n            *mt = JPEGMarkerType.RSTn;\r\n            version(JPEGDebug) writeln(\"RST3\");\r\n            break;\r\n        \r\n        case 0xFFD4:\r\n            // Restart\r\n            // TODO\r\n            *mt = JPEGMarkerType.RSTn;\r\n            version(JPEGDebug) writeln(\"RST4\");\r\n            break;\r\n        \r\n        case 0xFFD5:\r\n            // Restart\r\n            // TODO\r\n            *mt = JPEGMarkerType.RSTn;\r\n            version(JPEGDebug) writeln(\"RST5\");\r\n            break;\r\n        \r\n        case 0xFFD6:\r\n            // Restart\r\n            // TODO\r\n            *mt = JPEGMarkerType.RSTn;\r\n            version(JPEGDebug) writeln(\"RST6\");\r\n            break;\r\n        \r\n        case 0xFFD7:\r\n            // Restart\r\n            // TODO\r\n            *mt = JPEGMarkerType.RSTn;\r\n            version(JPEGDebug) writeln(\"RST7\");\r\n            break;\r\n        \r\n        case 0xFFD9:\r\n            // End of image\r\n            // TODO\r\n            *mt = JPEGMarkerType.EOI;\r\n            version(JPEGDebug) writeln(\"EOI\");\r\n            break;\r\n\r\n        default:\r\n            *mt = JPEGMarkerType.Unknown;\r\n            break;\r\n    }\r\n\r\n    return compound(true, \"\");\r\n}\r\n\r\nCompound!(bool, string) readJFIF(JPEGImage* jpg, InputStream istrm)\r\n{\r\n    ushort jfif_length = istrm.readNumeric!ushort(Endian.Big);\r\n\r\n    char[5] jfif_id = istrm.readChars!5;\r\n    if (jfif_id != \"JFIF\\0\")\r\n        return compound(false, \"loadJPEG error: illegal JFIF header\");\r\n\r\n    jpg.jfif.versionMajor = istrm.readNumeric!ubyte;\r\n    jpg.jfif.versionMinor = istrm.readNumeric!ubyte;\r\n    jpg.jfif.units = istrm.readNumeric!ubyte;\r\n    jpg.jfif.xDensity = istrm.readNumeric!ushort(Endian.Big);\r\n    jpg.jfif.yDensity = istrm.readNumeric!ushort(Endian.Big);\r\n    jpg.jfif.thumbnailWidth = istrm.readNumeric!ubyte;\r\n    jpg.jfif.thumbnailHeight = istrm.readNumeric!ubyte;\r\n\r\n    uint jfif_thumb_length = jpg.jfif.thumbnailWidth * jpg.jfif.thumbnailHeight * 3;\r\n    if (jfif_thumb_length > 0)\r\n    {\r\n        jpg.jfif.thumbnail = New!(ubyte[])(jfif_thumb_length);\r\n        istrm.readBytes(jpg.jfif.thumbnail.ptr, jfif_thumb_length);\r\n    }\r\n\r\n    version(JPEGDebug)\r\n    {\r\n        writefln(\"APP0/JFIF length: %s\", jfif_length);\r\n        writefln(\"APP0/JFIF identifier: %s\", jfif_id);\r\n        writefln(\"APP0/JFIF version major: %s\", jpg.jfif.versionMajor);\r\n        writefln(\"APP0/JFIF version minor: %s\", jpg.jfif.versionMinor);\r\n        writefln(\"APP0/JFIF units: %s\", jpg.jfif.units);\r\n        writefln(\"APP0/JFIF xdensity: %s\", jpg.jfif.xDensity);\r\n        writefln(\"APP0/JFIF ydensity: %s\", jpg.jfif.yDensity);\r\n        writefln(\"APP0/JFIF xthumbnail: %s\", jpg.jfif.thumbnailWidth);\r\n        writefln(\"APP0/JFIF ythumbnail: %s\", jpg.jfif.thumbnailHeight);\r\n    }\r\n\r\n    return compound(true, \"\");\r\n}\r\n\r\n/*\r\n * APP1 - EXIF, XMP, ExtendedXMP, QVCI, FLIR\r\n * APP2 - ICC, FPXR, MPF, PreviewImage\r\n * APP3 - Kodak Meta, Stim, PreviewImage\r\n * APP4 - Scalado, FPXR, PreviewImage\r\n * APP5 - RMETA, PreviewImage\r\n * APP6 - EPPIM, NITF, HP TDHD\r\n * APP7 - Pentax, Qualcomm\r\n * APP8 - SPIFF\r\n * APP9 - MediaJukebox\r\n * APP10 - PhotoStudio comment\r\n * APP11 - JPEG-HDR\r\n * APP12 - PictureInfo, Ducky\r\n * APP13 - Photoshop, Adobe CM\r\n * APP14 - Adobe\r\n * APP15 - GraphicConverter\r\n */\r\nCompound!(bool, string) readAPPn(JPEGImage* jpg, InputStream istrm, uint n)\r\n{\r\n    ushort app_length = istrm.readNumeric!ushort(Endian.Big);\r\n    ubyte[] app = New!(ubyte[])(app_length-2);\r\n    istrm.readBytes(app.ptr, app_length-2);\r\n\r\n    // TODO: interpret APP data (EXIF etc.) and save it somewhere.\r\n    // Maybe add a generic ImageInfo object for this?\r\n    Delete(app);\r\n\r\n    version(JPEGDebug)\r\n    {\r\n        writefln(\"APP%s length: %s\", n, app_length);\r\n    }\r\n\r\n    return compound(true, \"\");\r\n}\r\n\r\nCompound!(bool, string) readCOM(JPEGImage* jpg, InputStream istrm)\r\n{\r\n    ushort com_length = istrm.readNumeric!ushort(Endian.Big);\r\n    ubyte[] com = New!(ubyte[])(com_length-2);\r\n    istrm.readBytes(com.ptr, com_length-2);\r\n\r\n    version(JPEGDebug)\r\n    {\r\n        writefln(\"COM string: \\\"%s\\\"\", cast(string)com);\r\n        writefln(\"COM length: %s\", com_length);\r\n    }\r\n\r\n    // TODO: save COM data somewhere.\r\n    // Maybe add a generic ImageInfo object for this?\r\n    Delete(com);\r\n\r\n    return compound(true, \"\");\r\n}\r\n\r\nCompound!(bool, string) readDQT(JPEGImage* jpg, InputStream istrm)\r\n{\r\n    ushort dqt_length = istrm.readNumeric!ushort(Endian.Big);\r\n    version(JPEGDebug)\r\n    {\r\n        writefln(\"DQT length: %s\", dqt_length);\r\n    }\r\n\r\n    dqt_length -= 2;\r\n\r\n    while(dqt_length)\r\n    {\r\n        JPEGImage.DQT* dqt = jpg.addDQT();\r\n\r\n        ubyte bite = istrm.readNumeric!ubyte;\r\n        dqt.precision = bite.hiNibble;\r\n        dqt.tableId = bite.loNibble;\r\n\r\n        dqt_length--;\r\n\r\n        if (dqt.precision == 0)\r\n        {\r\n            dqt.table = New!(ubyte[])(64);\r\n            dqt_length -= 64;\r\n        }\r\n        else if (dqt.precision == 1)\r\n        {\r\n            dqt.table = New!(ubyte[])(128);\r\n            dqt_length -= 128;\r\n        }\r\n\r\n        istrm.readBytes(dqt.table.ptr, dqt.table.length);\r\n\r\n        version(JPEGDebug)\r\n        {\r\n            writefln(\"DQT precision: %s\", dqt.precision);\r\n            writefln(\"DQT table id: %s\", dqt.tableId);\r\n            writefln(\"DQT table: %s\", dqt.table);\r\n        }\r\n    }\r\n\r\n    return compound(true, \"\");\r\n}\r\n\r\nCompound!(bool, string) readSOF0(JPEGImage* jpg, InputStream istrm)\r\n{\r\n    ushort sof0_length = istrm.readNumeric!ushort(Endian.Big);\r\n    jpg.frame.isProgressive = false;\r\n    jpg.frame.precision = istrm.readNumeric!ubyte;\r\n    jpg.frame.height = istrm.readNumeric!ushort(Endian.Big);\r\n    jpg.frame.width = istrm.readNumeric!ushort(Endian.Big);\r\n    jpg.frame.componentsNum = istrm.readNumeric!ubyte;\r\n\r\n    version(JPEGDebug)\r\n    {\r\n        writefln(\"SOF length: %s\", sof0_length);\r\n        writefln(\"SOF precision: %s\", jpg.frame.precision);\r\n        writefln(\"SOF height: %s\", jpg.frame.height);\r\n        writefln(\"SOF width: %s\", jpg.frame.width);\r\n        writefln(\"SOF components: %s\", jpg.frame.componentsNum);\r\n    }\r\n\r\n    jpg.frame.components = New!(JPEGImage.FrameComponent[])(jpg.frame.componentsNum);\r\n\r\n    foreach(ref c; jpg.frame.components)\r\n    {\r\n        ubyte c_id = istrm.readNumeric!ubyte;\r\n        ubyte bite = istrm.readNumeric!ubyte;\r\n        c.hSubsampling = bite.hiNibble;\r\n        c.vSubsampling = bite.loNibble;\r\n        c.dqtTableId = istrm.readNumeric!ubyte;\r\n        version(JPEGDebug)\r\n        {\r\n            writefln(\"SOF component id: %s\", c_id);\r\n            writefln(\"SOF component %s hsubsampling: %s\", c_id, c.hSubsampling);\r\n            writefln(\"SOF component %s vsubsampling: %s\", c_id, c.vSubsampling);\r\n            writefln(\"SOF component %s table id: %s\", c_id, c.dqtTableId);\r\n        }\r\n    }\r\n\r\n    return compound(true, \"\");\r\n}\r\n\r\nCompound!(bool, string) readSOF2(JPEGImage* jpg, InputStream istrm)\r\n{\r\n    auto res = readSOF0(jpg, istrm);\r\n    jpg.frame.isProgressive = true;\r\n    return res;\r\n}\r\n\r\nCompound!(bool, string) readDHT(JPEGImage* jpg, InputStream istrm)\r\n{\r\n    ushort dht_length = istrm.readNumeric!ushort(Endian.Big);\r\n    version(JPEGDebug)\r\n    {\r\n        writefln(\"DHT length: %s\", dht_length);\r\n    }\r\n\r\n    dht_length -= 2;\r\n\r\n    while(dht_length > 0)\r\n    {\r\n        JPEGImage.DHT* dht = jpg.addDHT();\r\n\r\n        ubyte bite = istrm.readNumeric!ubyte;\r\n        dht_length--;\r\n        dht.clas = bite.hiNibble;\r\n        dht.tableId = bite.loNibble;\r\n\r\n        ubyte[16] dht_code_lengths;\r\n        istrm.readBytes(dht_code_lengths.ptr, 16);\r\n        dht_length -= 16;\r\n\r\n        version(JPEGDebug)\r\n        {\r\n            writefln(\"DHT class: %s (%s)\",\r\n                dht.clas,\r\n                dht.clas? \"AC\":\"DC\");\r\n            writefln(\"DHT tableId: %s\", dht.tableId);\r\n            writefln(\"DHT Huffman code lengths: %s\", dht_code_lengths);\r\n        }\r\n\r\n        // Read Huffman table\r\n        int totalCodes = reduce!(\"a + b\")(0, dht_code_lengths);\r\n        int storedCodes = 0;\r\n        ubyte treeLevel = 0;\r\n        ushort bits = 0;\r\n\r\n        while (storedCodes != totalCodes)\r\n        {\r\n            while (treeLevel < 15 &&\r\n                dht_code_lengths[treeLevel] == 0)\r\n            {\r\n                treeLevel++;\r\n                bits *= 2;\r\n            }\r\n\r\n            if (treeLevel < 16)\r\n            {\r\n                uint bitsNum = treeLevel + 1;\r\n                HuffmanCode code = HuffmanCode(bits, cast(ushort)bitsNum);\r\n\r\n                auto entry = HuffmanTableEntry(code, istrm.readNumeric!ubyte);\r\n                dht.huffmanTable.append(entry);\r\n\r\n                dht_length--;\r\n\r\n                storedCodes++;\r\n                bits++;\r\n                dht_code_lengths[treeLevel]--;\r\n            }\r\n        }\r\n\r\n        dht.huffmanTree = treeFromTable(dht.huffmanTable);\r\n    }\r\n\r\n    return compound(true, \"\");\r\n}\r\n\r\nCompound!(bool, string) readSOS(JPEGImage* jpg, InputStream istrm)\r\n{\r\n    ushort sos_length = istrm.readNumeric!ushort(Endian.Big);\r\n    jpg.scan.componentsNum = istrm.readNumeric!ubyte;\r\n\r\n    version(JPEGDebug)\r\n    {\r\n        writefln(\"SOS length: %s\", sos_length);\r\n        writefln(\"SOS components: %s\", jpg.scan.componentsNum);\r\n    }\r\n\r\n    jpg.scan.components = New!(JPEGImage.ScanComponent[])(jpg.scan.componentsNum);\r\n\r\n    foreach(ref c; jpg.scan.components)\r\n    {\r\n        ubyte c_id = istrm.readNumeric!ubyte;\r\n        ubyte bite = istrm.readNumeric!ubyte;\r\n        c.tableIdDC = bite.hiNibble;\r\n        c.tableIdAC = bite.loNibble;\r\n        version(JPEGDebug)\r\n        {\r\n            writefln(\"SOS component id: %s\", c_id);\r\n            writefln(\"SOS component %s DC table id: %s\", c_id, c.tableIdDC);\r\n            writefln(\"SOS component %s AC table id: %s\", c_id, c.tableIdAC);\r\n        }\r\n    }\r\n\r\n    jpg.scan.spectralSelectionStart = istrm.readNumeric!ubyte;\r\n    jpg.scan.spectralSelectionEnd = istrm.readNumeric!ubyte;\r\n    ubyte bite = istrm.readNumeric!ubyte;\r\n    jpg.scan.successiveApproximationBitHigh = bite.hiNibble;\r\n    jpg.scan.successiveApproximationBitLow = bite.loNibble;\r\n\r\n    version(JPEGDebug)\r\n    {\r\n        writefln(\"SOS spectral selection start: %s\", jpg.scan.spectralSelectionStart);\r\n        writefln(\"SOS spectral selection end: %s\", jpg.scan.spectralSelectionEnd);\r\n        writefln(\"SOS successive approximation bit: %s\", jpg.scan.successiveApproximationBitHigh);\r\n        writefln(\"SOS successive approximation bit low: %s\", jpg.scan.successiveApproximationBitLow);\r\n    }\r\n\r\n    return compound(true, \"\");\r\n}\r\n\r\nstruct ScanBitStream\r\n{\r\n    InputStream istrm;\r\n\r\n    bool endMarkerFound = false;\r\n    uint bytesRead = 0;\r\n    ubyte prevByte = 0x00;\r\n    ubyte curByte = 0x00;\r\n\r\n    ubyte readNextByte()\r\n    {\r\n        ubyte b = istrm.readNumeric!ubyte;\r\n        bytesRead++;\r\n        endMarkerFound = (prevByte == 0xFF && b == 0xD9);\r\n        assert(!endMarkerFound);\r\n        if (!endMarkerFound)\r\n        {\r\n            prevByte = b;\r\n            curByte = b;\r\n            return b;\r\n        }\r\n        else\r\n        {\r\n            curByte = 0;\r\n        }\r\n        return curByte;\r\n    }\r\n\r\n    bool readable()\r\n    {\r\n        return !istrm.readable || endMarkerFound;\r\n    }\r\n\r\n    uint bitPos = 0;\r\n\r\n    // Huffman decode a byte\r\n    Compound!(bool, string) decodeByte(HuffmanTreeNode* node, ubyte* result)\r\n    {\r\n        while(!node.isLeaf)\r\n        {\r\n            ubyte b = curByte;\r\n\r\n            bool bit = getBit(b, 7-bitPos);\r\n            bitPos++;\r\n            if (bitPos == 8)\r\n            {\r\n                bitPos = 0;\r\n                readNextByte();\r\n\r\n                if (b == 0xFF)\r\n                {\r\n                    b = curByte;\r\n                    if (b == 0x00)\r\n                    {\r\n                        readNextByte();\r\n                    }\r\n                }\r\n            }\r\n\r\n            if (bit)\r\n                node = node.right;\r\n            else\r\n                node = node.left;\r\n\r\n            if (node is null)\r\n                return compound(false, \"loadJPEG error: no Huffman code found\");\r\n        }\r\n\r\n        *result = node.ch;\r\n        return compound(true, \"\");\r\n    }\r\n\r\n    // Read len bits from stream to buffer\r\n    uint readBits(ubyte len)\r\n    {\r\n        uint buffer = 0;\r\n        uint i = 0;\r\n        uint by = 0;\r\n        uint bi = 0;\r\n\r\n        while (i < len)\r\n        {\r\n            ubyte b = curByte;\r\n\r\n            bool bit = getBit(b, 7-bitPos);\r\n            buffer = setBit(buffer, (by * 8 + bi), bit);\r\n\r\n            bi++;\r\n            if (bi == 8)\r\n            {\r\n                bi = 0;\r\n                by++;\r\n            }\r\n\r\n            i++;\r\n\r\n            bitPos++;\r\n            if (bitPos == 8)\r\n            {\r\n                bitPos = 0;\r\n                readNextByte();\r\n\r\n                if (b == 0xFF)\r\n                {\r\n                    b = curByte;\r\n                    if (b == 0x00)\r\n                        readNextByte();\r\n                }\r\n            }\r\n        }\r\n\r\n        return buffer;\r\n    }\r\n}\r\n\r\n/*\r\n *  Decodes compressed data and creates RGB image from it\r\n */\r\nCompound!(SuperImage, string) decodeScanData(\r\n    JPEGImage* jpg,\r\n    InputStream istrm,\r\n    SuperImageFactory imgFac)\r\n{\r\n    SuperImage img = imgFac.createImage(jpg.frame.width, jpg.frame.height, 3, 8);\r\n\r\n    MCU mcu;\r\n    foreach(ci, ref c; jpg.frame.components)\r\n    {\r\n        if (ci == 0)\r\n            mcu.createYBlocks(c.hSubsampling, c.vSubsampling);\r\n        else if (ci == 1)\r\n            mcu.createCbBlocks(c.hSubsampling, c.vSubsampling);\r\n        else if (ci == 2)\r\n            mcu.createCrBlocks(c.hSubsampling, c.vSubsampling);\r\n    }\r\n\r\n    Compound!(SuperImage, string) error(string errorMsg)\r\n    {\r\n        mcu.free();\r\n        if (img)\r\n        {\r\n            img.free();\r\n            img = null;\r\n        }\r\n        return compound(img, errorMsg);\r\n    }\r\n\r\n    // Decode DCT coefficient from bit buffer\r\n    int decodeCoef(uint buffer, ubyte numBits)\r\n    {\r\n        bool positive = getBit(buffer, 0);\r\n\r\n        int value = 0;\r\n        foreach(j; 0..numBits)\r\n        {\r\n            bool bit = getBit(buffer, numBits-1-j);\r\n            value = setBit(value, j, bit);\r\n        }\r\n\r\n        if (positive)\r\n            return value;\r\n        else\r\n            return value - 2^^numBits + 1;\r\n    }\r\n\r\n    static const ubyte[64] dezigzag =\r\n    [\r\n         0,  1,  8, 16,  9,  2,  3, 10,\r\n        17, 24, 32, 25, 18, 11,  4,  5,\r\n        12, 19, 26, 33, 40, 48, 41, 34,\r\n        27, 20, 13,  6,  7, 14, 21, 28,\r\n        35, 42, 49, 56, 57, 50, 43, 36,\r\n        29, 22, 15, 23, 30, 37, 44, 51,\r\n        58, 59, 52, 45, 38, 31, 39, 46,\r\n        53, 60, 61, 54, 47, 55, 62, 63\r\n    ];\r\n\r\n    if (jpg.scan.componentsNum != 3)\r\n    {\r\n        return error(format(\r\n                \"loadJPEG error: unsupported number of scan components: %s\",\r\n                jpg.scan.componentsNum));\r\n    }\r\n\r\n    // Store previous DC coefficients\r\n    int[3] dcCoefPrev;\r\n\r\n    if (jpg.dqt.length == 0)\r\n        return error(\"loadJPEG error: no DQTs found\");\r\n\r\n    ScanBitStream sbs;\r\n    sbs.endMarkerFound = false;\r\n    sbs.bytesRead = 0;\r\n    sbs.prevByte = 0x00;\r\n    sbs.curByte = 0x00;\r\n    sbs.istrm = istrm;\r\n    sbs.readNextByte();\r\n\r\n    uint numMCUsH = jpg.frame.width / mcu.width + ((jpg.frame.width % mcu.width) > 0);\r\n    uint numMCUsV = jpg.frame.height / mcu.height + ((jpg.frame.height % mcu.height) > 0);\r\n\r\n    // Read MCUs\r\n    foreach(mcuY; 0..numMCUsV)\r\n    foreach(mcuX; 0..numMCUsH)\r\n    {\r\n        // Read MCU for each channel\r\n        foreach(ci, ref c; jpg.scan.components)\r\n        {\r\n            auto tableDC = jpg.getHuffmanTable(0, c.tableIdDC);\r\n            auto tableAC = jpg.getHuffmanTable(1, c.tableIdAC);\r\n\r\n            if (tableDC is null)\r\n                return error(\"loadJPEG error: illegal DC table index in MCU component\");\r\n            if (tableAC is null)\r\n                return error(\"loadJPEG error: illegal AC table index in MCU component\");\r\n\r\n            auto component = jpg.frame.components[ci];\r\n            auto hblocks = component.hSubsampling;\r\n            auto vblocks = component.vSubsampling;\r\n            auto dqtTableId = component.dqtTableId;\r\n            if (dqtTableId >= jpg.dqt.length)\r\n                return error(\"loadJPEG error: illegal DQT table index in MCU component\");\r\n\r\n            // Read 8x8 blocks\r\n            foreach(by; 0..vblocks)\r\n            foreach(bx; 0..hblocks)\r\n            {\r\n                int[8*8] block;\r\n\r\n                // Read DC coefficient\r\n                ubyte dcDiffLen;\r\n                auto res = sbs.decodeByte(tableDC.huffmanTree, &dcDiffLen);\r\n                if (!res[0]) return error(res[1]);\r\n\r\n                if (dcDiffLen > 0)\r\n                {\r\n                    uint dcBuffer = sbs.readBits(dcDiffLen);\r\n                    dcCoefPrev[ci] += decodeCoef(dcBuffer, dcDiffLen);\r\n                }\r\n\r\n                block[0] = dcCoefPrev[ci];\r\n\r\n                // Read AC coefficients\r\n                {\r\n                    uint i = 1;\r\n                    bool eob = false;\r\n                    while (!eob && i < 64)\r\n                    {\r\n                        ubyte code;\r\n                        res = sbs.decodeByte(tableAC.huffmanTree, &code);\r\n                        if (!res[0]) return error(res[1]);\r\n\r\n                        if (code == 0x00) // EOB, all next values are zero\r\n                            eob = true;\r\n                        else if (code == 0xF0) // ZRL, next 16 values are zero\r\n                        {\r\n                            foreach(j; 0..16)\r\n                            if (i < 64)\r\n                            {\r\n                                block[i] = 0;\r\n                                i++;\r\n                            }\r\n                        }\r\n                        else\r\n                        {\r\n                            ubyte hi = hiNibble(code);\r\n                            ubyte lo = loNibble(code);\r\n\r\n                            uint zeroes = hi;\r\n                            foreach(j; 0..zeroes)\r\n                            if (i < 64)\r\n                            {\r\n                                block[i] = 0;\r\n                                i++;\r\n                            }\r\n\r\n                            int acCoef = 0;\r\n                            if (lo > 0)\r\n                            {\r\n                                uint acBuffer = sbs.readBits(lo);\r\n                                acCoef = decodeCoef(acBuffer, lo);\r\n                            }\r\n\r\n                            if (i < 64)\r\n                                block[i] = acCoef;\r\n\r\n                            i++;\r\n                        }\r\n                    }\r\n                }\r\n\r\n                // Multiply block by quantization matrix\r\n                foreach(i, ref v; block)\r\n                    v *= jpg.dqt[dqtTableId].table[i];\r\n\r\n                // Convert matrix from zig-zag order to normal order\r\n                int[8*8] dctMatrix;\r\n\r\n                foreach(i, v; block)\r\n                    dctMatrix[dezigzag[i]] = v;\r\n\r\n                idct64(dctMatrix.ptr);\r\n\r\n                // Copy the matrix into corresponding channel\r\n                int* outMatrixPtr;\r\n                if (ci == 0)\r\n                    outMatrixPtr = mcu.yBlocks[by * hblocks + bx].ptr;\r\n                else if (ci == 1)\r\n                    outMatrixPtr = mcu.cbBlocks[by * hblocks + bx].ptr;\r\n                else if (ci == 2)\r\n                    outMatrixPtr = mcu.crBlocks[by * hblocks + bx].ptr;\r\n                else\r\n                    return error(\"loadJPEG error: illegal component index\");\r\n\r\n                for(uint i = 0; i < 64; i++)\r\n                    outMatrixPtr[i] = dctMatrix[i];\r\n            }\r\n        }\r\n\r\n        // Convert MCU from YCbCr to RGB\r\n        foreach(y; 0..mcu.height) // Pixel coordinates in MCU\r\n        foreach(x; 0..mcu.width)\r\n        {\r\n            Color4f col = mcu.getPixel(x, y);\r\n\r\n            // Pixel coordinates in image\r\n            uint ix = mcuX * mcu.width + x;\r\n            uint iy = mcuY * mcu.height + y;\r\n\r\n            if (ix < img.width && iy < img.height)\r\n                img[ix, iy] = col;\r\n        }\r\n    }\r\n\r\n    version(JPEGDebug)\r\n    {\r\n        writefln(\"Bytes read: %s\", sbs.bytesRead);\r\n    }\r\n\r\n    mcu.free();\r\n\r\n    return compound(img, \"\");\r\n}\r\n\r\n/*\r\n * MCU struct keeps a storage for one Minimal Code Unit\r\n * and provides a generalized interface for decoding\r\n * images with different subsampling modes.\r\n * Decoder should read 8x8 blocks one by one for each channel\r\n * and fill corresponding arrays in MCU.\r\n */\r\nstruct MCU\r\n{\r\n    uint width;\r\n    uint height;\r\n\r\n    alias Block = int[8*8];\r\n    Block[] yBlocks;\r\n    Block[] cbBlocks;\r\n    Block[] crBlocks;\r\n\r\n    uint ySamplesH, ySamplesV;\r\n    uint cbSamplesH, cbSamplesV;\r\n    uint crSamplesH, crSamplesV;\r\n\r\n    uint yWidth, yHeight;\r\n    uint cbWidth, cbHeight;\r\n    uint crWidth, crHeight;\r\n\r\n    void createYBlocks(uint hsubsampling, uint vsubsampling)\r\n    {\r\n        yBlocks = New!(Block[])(hsubsampling * vsubsampling);\r\n\r\n        width = hsubsampling * 8;\r\n        height = vsubsampling * 8;\r\n\r\n        ySamplesH = hsubsampling;\r\n        ySamplesV = vsubsampling;\r\n\r\n        yWidth = width / ySamplesH;\r\n        yHeight = height / ySamplesV;\r\n    }\r\n\r\n    void createCbBlocks(uint hsubsampling, uint vsubsampling)\r\n    {\r\n        cbBlocks = New!(Block[])(hsubsampling * vsubsampling);\r\n\r\n        cbSamplesH = hsubsampling;\r\n        cbSamplesV = vsubsampling;\r\n\r\n        cbWidth = width / cbSamplesH;\r\n        cbHeight = height / cbSamplesV;\r\n    }\r\n\r\n    void createCrBlocks(uint hsubsampling, uint vsubsampling)\r\n    {\r\n        crBlocks = New!(Block[])(hsubsampling * vsubsampling);\r\n\r\n        crSamplesH = hsubsampling;\r\n        crSamplesV = vsubsampling;\r\n\r\n        crWidth = width / crSamplesH;\r\n        crHeight = height / crSamplesV;\r\n    }\r\n\r\n    void free()\r\n    {\r\n        if (yBlocks.length) Delete(yBlocks);\r\n        if (cbBlocks.length) Delete(cbBlocks);\r\n        if (crBlocks.length) Delete(crBlocks);\r\n    }\r\n\r\n    Color4f getPixel(uint x, uint y) // coordinates relative to upper-left MCU corner\r\n    {\r\n        // Y block coordinates\r\n        uint ybx = x / yWidth;\r\n        uint yby = y / yHeight;\r\n        uint ybi = yby * ySamplesH + ybx;\r\n\r\n        // Pixel coordinates in Y block\r\n        uint ybpx = x - ybx * yWidth;\r\n        uint ybpy = y - yby * yHeight;\r\n\r\n        // Cb block coordinates\r\n        uint cbx = x / cbWidth;\r\n        uint cby = y / cbHeight;\r\n        uint cbi = cby * cbSamplesH + cbx;\r\n\r\n        // Pixel coordinates in Cb block\r\n        uint cbpx = (x - cbx * cbWidth)  / ySamplesH;\r\n        uint cbpy = (y - cby * cbHeight) / ySamplesV;\r\n\r\n        // Cr block coordinates\r\n        uint crx = x / crWidth;\r\n        uint cry = y / crHeight;\r\n        uint cri = cry * crSamplesH + crx;\r\n\r\n        // Pixel coordinates in Cr block\r\n        uint crpx = (x - crx * crWidth)  / ySamplesH;\r\n        uint crpy = (y - cry * crHeight) / ySamplesV;\r\n\r\n        // Get color components\r\n        float Y  = cast(float)yBlocks [ybi][ybpy * 8 + ybpx] + 128.0f;\r\n        float Cb = cast(float)cbBlocks[cbi][cbpy * 8 + cbpx];\r\n        float Cr = cast(float)crBlocks[cri][crpy * 8 + crpx];\r\n\r\n        // Convert from YCbCr to RGB\r\n        Color4f col;\r\n        col.r = Y + 1.402f * Cr;\r\n        col.g = Y - 0.34414f * Cb - 0.71414f * Cr;\r\n        col.b = Y + 1.772f * Cb;\r\n        col = col / 255.0f;\r\n        col.a = 1.0f;\r\n\r\n        return col;\r\n    }\r\n}\r\n/*\r\n * Inverse discrete cosine transform (DCT) for 64x64 blocks\r\n *\r\n * The input coefficients should already have been multiplied by the\r\n * appropriate quantization table. We use fixed-point computation, with the\r\n * number of bits for the fractional component varying over the intermediate\r\n * stages.\r\n *\r\n * For more on the actual algorithm, see Z. Wang, \"Fast algorithms for the\r\n * discrete W transform and for the discrete Fourier transform\", IEEE Trans. on\r\n * ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984.\r\n */\r\nvoid idct64(int* src)\r\n{\r\n    enum blockSize = 64; // A DCT block is 8x8.\r\n\r\n    enum w1 = 2841; // 2048*sqrt(2)*cos(1*pi/16)\r\n    enum w2 = 2676; // 2048*sqrt(2)*cos(2*pi/16)\r\n    enum w3 = 2408; // 2048*sqrt(2)*cos(3*pi/16)\r\n    enum w5 = 1609; // 2048*sqrt(2)*cos(5*pi/16)\r\n    enum w6 = 1108; // 2048*sqrt(2)*cos(6*pi/16)\r\n    enum w7 = 565; // 2048*sqrt(2)*cos(7*pi/16)\r\n\r\n    enum w1pw7 = w1 + w7;\r\n    enum w1mw7 = w1 - w7;\r\n    enum w2pw6 = w2 + w6;\r\n    enum w2mw6 = w2 - w6;\r\n    enum w3pw5 = w3 + w5;\r\n    enum w3mw5 = w3 - w5;\r\n\r\n    enum r2 = 181; // 256/sqrt(2)\r\n\r\n    // Horizontal 1-D IDCT.\r\n    for (uint y = 0; y < 8; y++)\r\n    {\r\n        int y8 = y * 8;\r\n        // If all the AC components are zero, then the IDCT is trivial.\r\n        if (src[y8+1] == 0 && src[y8+2] == 0 && src[y8+3] == 0 &&\r\n            src[y8+4] == 0 && src[y8+5] == 0 && src[y8+6] == 0 && src[y8+7] == 0)\r\n        {\r\n            int dc = src[y8+0] << 3;\r\n            src[y8+0] = dc;\r\n            src[y8+1] = dc;\r\n            src[y8+2] = dc;\r\n            src[y8+3] = dc;\r\n            src[y8+4] = dc;\r\n            src[y8+5] = dc;\r\n            src[y8+6] = dc;\r\n            src[y8+7] = dc;\r\n            continue;\r\n        }\r\n\r\n        // Prescale.\r\n        int x0 = (src[y8+0] << 11) + 128;\r\n        int x1 = src[y8+4] << 11;\r\n        int x2 = src[y8+6];\r\n        int x3 = src[y8+2];\r\n        int x4 = src[y8+1];\r\n        int x5 = src[y8+7];\r\n        int x6 = src[y8+5];\r\n        int x7 = src[y8+3];\r\n\r\n        // Stage 1.\r\n        int x8 = w7 * (x4 + x5);\r\n        x4 = x8 + w1mw7*x4;\r\n        x5 = x8 - w1pw7*x5;\r\n        x8 = w3 * (x6 + x7);\r\n        x6 = x8 - w3mw5*x6;\r\n        x7 = x8 - w3pw5*x7;\r\n\r\n        // Stage 2.\r\n        x8 = x0 + x1;\r\n        x0 -= x1;\r\n        x1 = w6 * (x3 + x2);\r\n        x2 = x1 - w2pw6*x2;\r\n        x3 = x1 + w2mw6*x3;\r\n        x1 = x4 + x6;\r\n        x4 -= x6;\r\n        x6 = x5 + x7;\r\n        x5 -= x7;\r\n\r\n        // Stage 3.\r\n        x7 = x8 + x3;\r\n        x8 -= x3;\r\n        x3 = x0 + x2;\r\n        x0 -= x2;\r\n        x2 = (r2*(x4+x5) + 128) >> 8;\r\n        x4 = (r2*(x4-x5) + 128) >> 8;\r\n\r\n        // Stage 4.\r\n        src[y8+0] = (x7 + x1) >> 8;\r\n        src[y8+1] = (x3 + x2) >> 8;\r\n        src[y8+2] = (x0 + x4) >> 8;\r\n        src[y8+3] = (x8 + x6) >> 8;\r\n        src[y8+4] = (x8 - x6) >> 8;\r\n        src[y8+5] = (x0 - x4) >> 8;\r\n        src[y8+6] = (x3 - x2) >> 8;\r\n        src[y8+7] = (x7 - x1) >> 8;\r\n    }\r\n\r\n    // Vertical 1-D IDCT.\r\n    for (uint x = 0; x < 8; x++)\r\n    {\r\n        // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial.\r\n        // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so\r\n        // we do not bother to check for the all-zero case.\r\n\r\n        // Prescale.\r\n        int y0 = (src[8*0+x] << 8) + 8192;\r\n        int y1 = src[8*4+x] << 8;\r\n        int y2 = src[8*6+x];\r\n        int y3 = src[8*2+x];\r\n        int y4 = src[8*1+x];\r\n        int y5 = src[8*7+x];\r\n        int y6 = src[8*5+x];\r\n        int y7 = src[8*3+x];\r\n\r\n        // Stage 1.\r\n        int y8 = w7*(y4+y5) + 4;\r\n        y4 = (y8 + w1mw7*y4) >> 3;\r\n        y5 = (y8 - w1pw7*y5) >> 3;\r\n        y8 = w3*(y6+y7) + 4;\r\n        y6 = (y8 - w3mw5*y6) >> 3;\r\n        y7 = (y8 - w3pw5*y7) >> 3;\r\n\r\n        // Stage 2.\r\n        y8 = y0 + y1;\r\n        y0 -= y1;\r\n        y1 = w6*(y3+y2) + 4;\r\n        y2 = (y1 - w2pw6*y2) >> 3;\r\n        y3 = (y1 + w2mw6*y3) >> 3;\r\n        y1 = y4 + y6;\r\n        y4 -= y6;\r\n        y6 = y5 + y7;\r\n        y5 -= y7;\r\n\r\n        // Stage 3.\r\n        y7 = y8 + y3;\r\n        y8 -= y3;\r\n        y3 = y0 + y2;\r\n        y0 -= y2;\r\n        y2 = (r2*(y4+y5) + 128) >> 8;\r\n        y4 = (r2*(y4-y5) + 128) >> 8;\r\n\r\n        // Stage 4.\r\n        src[8*0+x] = (y7 + y1) >> 14;\r\n        src[8*1+x] = (y3 + y2) >> 14;\r\n        src[8*2+x] = (y0 + y4) >> 14;\r\n        src[8*3+x] = (y8 + y6) >> 14;\r\n        src[8*4+x] = (y8 - y6) >> 14;\r\n        src[8*5+x] = (y0 - y4) >> 14;\r\n        src[8*6+x] = (y3 - y2) >> 14;\r\n        src[8*7+x] = (y7 - y1) >> 14;\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/image/io/package.d",
    "content": "/*\nCopyright (c) 2014-2025 Timur Gafarov, Martin Cejp\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Load and save images\n *\n * Copyright: Timur Gafarov, Martin Cejp 2014-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov, Martin Cejp\n */\nmodule dlib.image.io;\n\nimport std.path;\nimport dlib.image.image;\nimport dlib.image.animation;\nimport dlib.image.hdri;\n\npublic\n{\n    import dlib.image.io.bmp;\n    import dlib.image.io.hdr;\n    import dlib.image.io.png;\n    import dlib.image.io.tga;\n    import dlib.image.io.jpeg;\n}\n\nclass ImageLoadException : Exception\n{\n    this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)\n    {\n        super(msg, file, line, next);\n    }\n}\n\n/**\n * Saves an image to file, selects encoder by filename extension\n */\nvoid saveImage(SuperImage img, string filename)\n{\n    switch(filename.extension)\n    {\n        case \".png\", \".PNG\":\n            img.savePNG(filename);\n            break;\n        case \".bmp\", \".BMP\":\n            img.saveBMP(filename);\n            break;\n        case \".tga\", \".TGA\":\n            img.saveTGA(filename);\n            break;\n        default:\n            assert(0, \"Image I/O error: unsupported image format or illegal extension\");\n    }\n}\n\n/**\n * Loads an image from file, selects decoder by filename extension\n */\nSuperImage loadImage(string filename)\n{\n    switch(filename.extension)\n    {\n        case \".bmp\", \".BMP\":\n            return loadBMP(filename);\n        case \".hdr\", \".HDR\":\n            return loadHDR(filename);\n        case \".jpg\", \".JPG\", \".jpeg\", \".JPEG\":\n            return loadJPEG(filename);\n        case \".png\", \".PNG\":\n            return loadPNG(filename);\n        case \".tga\", \".TGA\":\n            return loadTGA(filename);\n        default:\n            assert(0, \"Image I/O error: unsupported image format or illegal extension\");\n    }\n}\n\n/**\n * Loads an animated image from file, selects decoder by filename extension\n */\nSuperAnimatedImage loadAnimatedImage(string filename)\n{\n    switch(filename.extension)\n    {\n        case \".png\", \".apng\", \".PNG\", \".APNG\":\n            return loadAPNG(filename);\n        default:\n            assert(0, \"Image I/O error: unsupported image format or illegal extension\");\n    }\n}\n\n/**\n * Saves an animated image to file, selects encoder by filename extension\n */\nvoid saveAnimatedImage(SuperAnimatedImage img, string filename)\n{\n    switch(filename.extension)\n    {\n        case \".png\", \".PNG\", \".apng\", \".APNG\":\n            img.saveAPNG(filename);\n            break;\n        default:\n            assert(0, \"Image I/O error: unsupported image format or illegal extension\");\n    }\n}\n\n/**\n * Loads an HDR image from file, selects decoder by filename extension\n */\nSuperImage loadHDRImage(string filename)\n{\n    switch(filename.extension)\n    {\n        case \".hdr\", \".HDR\":\n            return loadHDR(filename);\n        default:\n            assert(0, \"Image I/O error: unsupported image format or illegal extension\");\n    }\n}\n\n/**\n * Saves an HDR to file, selects encoder by filename extension\n */\nvoid saveHDRImage(SuperHDRImage img, string filename)\n{\n    switch(filename.extension)\n    {\n        case \".hdr\", \".HDR\":\n            img.saveHDR(filename);\n            break;\n        default:\n            assert(0, \"Image I/O error: unsupported image format or illegal extension\");\n    }\n}\n"
  },
  {
    "path": "dlib/image/io/png.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov, Martin Cejp, Vadim Lopatin\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Decode and encode PNG/APNG images\r\n *\r\n * Copyright: Timur Gafarov, Martin Cejp, Vadim Lopatin 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov, Martin Cejp, Vadim Lopatin\r\n */\r\nmodule dlib.image.io.png;\r\n\r\nimport std.stdio;\r\nimport std.math;\r\nimport std.string;\r\nimport std.range;\r\nimport dlib.core.memory;\r\nimport dlib.core.stream;\r\nimport dlib.core.compound;\r\nimport dlib.filesystem.local;\r\nimport dlib.math.utils;\r\nimport dlib.math.interpolation;\r\nimport dlib.coding.zlib;\r\nimport dlib.image.color;\r\nimport dlib.image.image;\r\nimport dlib.image.animation;\r\nimport dlib.image.io;\r\n\r\n// uncomment this to see debug messages:\r\n//version = PNGDebug;\r\n\r\nstatic const ubyte[8] PNGSignature = [137, 80, 78, 71, 13, 10, 26, 10];\r\n\r\n// Standard chunks\r\nstatic const ubyte[4] IHDR = ['I', 'H', 'D', 'R']; // Image header\r\nstatic const ubyte[4] IEND = ['I', 'E', 'N', 'D']; // Image end\r\nstatic const ubyte[4] IDAT = ['I', 'D', 'A', 'T']; // Image data\r\nstatic const ubyte[4] PLTE = ['P', 'L', 'T', 'E']; // Palette\r\nstatic const ubyte[4] tRNS = ['t', 'R', 'N', 'S']; // Transparency\r\nstatic const ubyte[4] bKGD = ['b', 'K', 'G', 'D']; // Background color\r\nstatic const ubyte[4] tEXt = ['t', 'E', 'X', 't']; // Textual data (uncompressed)\r\nstatic const ubyte[4] zTXt = ['z', 'T', 'X', 't']; // Textual data (zlib compressed)\r\nstatic const ubyte[4] iTXt = ['i', 'T', 'X', 't']; // International text\r\n\r\n// Extension chunks\r\nstatic const ubyte[4] oFFs = ['o', 'F', 'F', 's']; // Image offset\r\nstatic const ubyte[4] pCAL = ['p', 'C', 'A', 'L']; // Calibration of pixel values\r\nstatic const ubyte[4] sCAL = ['s', 'C', 'A', 'L']; // Physical scale of image subject\r\nstatic const ubyte[4] gIFg = ['g', 'I', 'F', 'g']; // GIF graphic control\r\nstatic const ubyte[4] gIFx = ['g', 'I', 'F', 'x']; // GIF application\r\nstatic const ubyte[4] gIFt = ['g', 'I', 'F', 't']; // GIF plain text (deprecated)\r\nstatic const ubyte[4] fRAc = ['f', 'R', 'A', 'c']; // Fractal image parameters\r\nstatic const ubyte[4] sTER = ['s', 'T', 'E', 'R']; // Indicator of stereo image\r\nstatic const ubyte[4] dSIG = ['d', 'S', 'I', 'G']; // Digital signature\r\n\r\n// ImageMagick chunks\r\nstatic const ubyte[4] vpAg = ['v', 'p', 'A', 'g']; // VirtualPage Tags\r\n\r\n// APNG chunks\r\nstatic const ubyte[4] acTL = ['a', 'c', 'T', 'L']; // Animation control\r\nstatic const ubyte[4] fcTL = ['f', 'c', 'T', 'L']; // Frame control\r\nstatic const ubyte[4] fdAT = ['f', 'd', 'A', 'T']; // Frame data\r\n\r\nenum ColorType: ubyte\r\n{\r\n    Greyscale = 0,      // allowed bit depths: 1, 2, 4, 8 and 16\r\n    RGB = 2,            // allowed bit depths: 8 and 16\r\n    Palette = 3,        // allowed bit depths: 1, 2, 4 and 8\r\n    GreyscaleAlpha = 4, // allowed bit depths: 8 and 16\r\n    RGBA = 6,           // allowed bit depths: 8 and 16\r\n    Any = 7             // one of the above\r\n}\r\n\r\nenum FilterMethod: ubyte\r\n{\r\n    None = 0,\r\n    Sub = 1,\r\n    Up = 2,\r\n    Average = 3,\r\n    Paeth = 4\r\n}\r\n\r\nstruct PNGChunk\r\n{\r\n    uint length;\r\n    ubyte[4] type;\r\n    ubyte[] data;\r\n    uint crc;\r\n\r\n    void free()\r\n    {\r\n        if (data.ptr)\r\n            Delete(data);\r\n    }\r\n}\r\n\r\nstruct PNGHeader\r\n{\r\n    union\r\n    {\r\n        struct\r\n        {\r\n            uint width;\r\n            uint height;\r\n            ubyte bitDepth;\r\n            ubyte colorType;\r\n            ubyte compressionMethod;\r\n            ubyte filterMethod;\r\n            ubyte interlaceMethod;\r\n        };\r\n        ubyte[13] bytes;\r\n    }\r\n}\r\n\r\nstruct AnimationControlChunk\r\n{\r\n    union\r\n    {\r\n        struct\r\n        {\r\n            uint numFrames;\r\n            uint numPlays;\r\n        };\r\n        ubyte[8] bytes;\r\n    }\r\n\r\n    void readFromBuffer(ubyte[] data)\r\n    {\r\n        *(&numFrames) = *(cast(uint*)data.ptr);\r\n        numFrames = bigEndian(numFrames);\r\n        *(&numPlays) = *(cast(uint*)(data.ptr+4));\r\n        numPlays = bigEndian(numPlays);\r\n    }\r\n}\r\n\r\nstruct OffsetChunk\r\n{\r\n    int posX;\r\n    int posY;\r\n    ubyte unitSpecifier;\r\n\r\n    void readFromBuffer(ubyte[] data)\r\n    {\r\n        *(&posX) = *(cast(int*)data.ptr);\r\n        posX = bigEndian(posX);\r\n        *(&posY) = *(cast(int*)(data.ptr+4));\r\n        posY = bigEndian(posY);\r\n        *(&unitSpecifier) = *(data.ptr+8);\r\n    }\r\n}\r\n\r\nenum DisposeOp: ubyte\r\n{\r\n    None = 0,\r\n    Background = 1,\r\n    Previous = 2\r\n}\r\n\r\nenum BlendOp: ubyte\r\n{\r\n    Source = 0,\r\n    Over = 1\r\n}\r\n\r\nstruct FrameControlChunk\r\n{\r\n    union\r\n    {\r\n        struct\r\n        {\r\n            uint sequenceNumber;\r\n            uint width;\r\n            uint height;\r\n            uint x;\r\n            uint y;\r\n            ushort delayNumerator;\r\n            ushort delayDenominator;\r\n            ubyte disposeOp;\r\n            ubyte blendOp;\r\n        };\r\n        ubyte[26] bytes;\r\n    }\r\n\r\n    void readFromBuffer(ubyte[] data)\r\n    {\r\n        *(&sequenceNumber) = *(cast(uint*)data.ptr);\r\n        sequenceNumber = bigEndian(sequenceNumber);\r\n        *(&width) = *(cast(uint*)(data.ptr+4));\r\n        width = bigEndian(width);\r\n        *(&height) = *(cast(uint*)(data.ptr+8));\r\n        height = bigEndian(height);\r\n        *(&x) = *(cast(uint*)(data.ptr+12));\r\n        x = bigEndian(x);\r\n        *(&y) = *(cast(uint*)(data.ptr+16));\r\n        y = bigEndian(y);\r\n        *(&delayNumerator) = *(cast(ushort*)(data.ptr+20));\r\n        delayNumerator = bigEndian(delayNumerator);\r\n        *(&delayDenominator) = *(cast(ushort*)(data.ptr+22));\r\n        delayDenominator = bigEndian(delayDenominator);\r\n        disposeOp = data[24];\r\n        blendOp = data[25];\r\n    }\r\n}\r\n\r\nstruct PNGImage\r\n{\r\n    // Common PNG data\r\n    PNGHeader hdr;\r\n    uint numChannels;\r\n    uint bitDepth;\r\n    uint bytesPerChannel;\r\n    bool isAnimated = false;\r\n\r\n    // Data for indexed PNG\r\n    ubyte[] palette;\r\n    ubyte[] transparency;\r\n    uint paletteSize = 0;\r\n\r\n    // APNG data\r\n    uint numFrames = 1;\r\n    uint numLoops = 0;\r\n    bool decodingFirstFrame;\r\n    FrameControlChunk frame;\r\n\r\n    // Offset\r\n    OffsetChunk offset;\r\n\r\n    ZlibDecoder decoder;\r\n\r\n    ubyte[] frameBuffer;\r\n    uint frameSize;\r\n\r\n    ubyte[] filteredBuffer;\r\n    uint filteredBufferSize;\r\n\r\n    void initDecoder()\r\n    {\r\n        ubyte[] buffer;\r\n\r\n        if (decoder.buffer.length)\r\n        {\r\n            buffer = decoder.buffer;\r\n        }\r\n        else\r\n        {\r\n            uint bufferLength = hdr.width * hdr.height * numChannels * bytesPerChannel + hdr.height;\r\n            buffer = New!(ubyte[])(bufferLength);\r\n        }\r\n\r\n        decoder = ZlibDecoder(buffer);\r\n    }\r\n\r\n    void initFrameBuffer()\r\n    {\r\n        if (frameBuffer.length)\r\n            Delete(frameBuffer);\r\n\r\n        if (filteredBuffer.length)\r\n            Delete(filteredBuffer);\r\n\r\n        frameBuffer = New!(ubyte[])(hdr.width * hdr.height * numChannels * bytesPerChannel);\r\n        filteredBuffer = New!(ubyte[])(hdr.width * hdr.height * numChannels * bytesPerChannel);\r\n    }\r\n\r\n    void free()\r\n    {\r\n        if (decoder.buffer.length)\r\n            Delete(decoder.buffer);\r\n\r\n        if (frameBuffer.length)\r\n            Delete(frameBuffer);\r\n\r\n        if (filteredBuffer.length)\r\n            Delete(filteredBuffer);\r\n\r\n        if (palette.length)\r\n            Delete(palette);\r\n\r\n        if (transparency.length)\r\n            Delete(transparency);\r\n    }\r\n}\r\n\r\nclass PNGLoadException: ImageLoadException\r\n{\r\n    this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)\r\n    {\r\n        super(msg, file, line, next);\r\n    }\r\n}\r\n\r\n/**\r\n * Load PNG from file using local FileSystem.\r\n * Causes GC allocation\r\n */\r\nSuperImage loadPNG(string filename)\r\n{\r\n    InputStream input = openForInput(filename);\r\n    ubyte[] data = New!(ubyte[])(cast(size_t)input.size);\r\n    input.fillArray(data);\r\n    ArrayStream arrStrm = New!ArrayStream(data);\r\n    auto img = loadPNG(arrStrm);\r\n    Delete(arrStrm);\r\n    Delete(data);\r\n    input.close();\r\n    return img;\r\n}\r\n\r\n/**\r\n * Load animated PNG (APNG) from file using local FileSystem.\r\n * Causes GC allocation\r\n */\r\nSuperAnimatedImage loadAPNG(string filename)\r\n{\r\n    InputStream input = openForInput(filename);\r\n    ubyte[] data = New!(ubyte[])(cast(size_t)input.size);\r\n    input.fillArray(data);\r\n    ArrayStream arrStrm = New!ArrayStream(data);\r\n    auto img = loadAPNG(arrStrm);\r\n    Delete(arrStrm);\r\n    Delete(data);\r\n    input.close();\r\n    return img;\r\n}\r\n\r\n/**\r\n * Save PNG to file using local FileSystem.\r\n * Causes GC allocation\r\n */\r\nvoid savePNG(SuperImage img, string filename)\r\n{\r\n    OutputStream output = openForOutput(filename);\r\n    Compound!(bool, string) res = savePNG(img, output);\r\n    output.close();\r\n\r\n    if (!res[0])\r\n        throw new PNGLoadException(res[1]);\r\n}\r\n\r\n/**\r\n * Save APNG to file using local FileSystem.\r\n * Causes GC allocation\r\n */\r\nvoid saveAPNG(SuperAnimatedImage img, string filename)\r\n{\r\n    OutputStream output = openForOutput(filename);\r\n    Compound!(bool, string) res = saveAPNG(img, output);\r\n    output.close();\r\n\r\n    if (!res[0])\r\n        throw new PNGLoadException(res[1]);\r\n}\r\n\r\n/**\r\n * Load PNG from stream using default image factory.\r\n * Causes GC allocation\r\n */\r\nSuperImage loadPNG(InputStream istrm)\r\n{\r\n    Compound!(SuperImage, string) res =\r\n        loadPNG(istrm, defaultImageFactory);\r\n    if (res[0] is null)\r\n        throw new PNGLoadException(res[1]);\r\n    else\r\n        return res[0];\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    import std.base64;\r\n\r\n    InputStream png() {\r\n        string minimal =\r\n            \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADklEQVR42mL4z8AAEGAAAwEBAGb9nyQAAAAASUVORK5CYII=\";\r\n\r\n        ubyte[] bytes = Base64.decode(minimal);\r\n        return new ArrayStream(bytes, bytes.length);\r\n    }\r\n\r\n    SuperImage img = loadPNG(png());\r\n\r\n    assert(img.width == 1);\r\n    assert(img.height == 1);\r\n    assert(img.channels == 3);\r\n    assert(img.pixelSize == 3);\r\n    assert(img.data == [0xff, 0x00, 0x00]);\r\n\r\n    createDir(\"tests\", false);\r\n    savePNG(img, \"tests/minimal.png\");\r\n    loadPNG(\"tests/minimal.png\");\r\n}\r\n\r\n/**\r\n * Load animated PNG (APNG) from stream using default animated image factory.\r\n * Causes GC allocation\r\n */\r\nSuperAnimatedImage loadAPNG(InputStream istrm)\r\n{\r\n    Compound!(SuperImage, string) res =\r\n        loadPNG(istrm, animatedImageFactory);\r\n    if (res[0] is null)\r\n        throw new PNGLoadException(res[1]);\r\n    else\r\n        return cast(SuperAnimatedImage)res[0];\r\n}\r\n\r\n/**\r\n * Load PNG from stream using specified image factory.\r\n * GC-free\r\n */\r\nCompound!(SuperImage, string) loadPNG(\r\n    InputStream istrm,\r\n    SuperImageFactory imgFac)\r\n{\r\n    PNGImage png;\r\n\r\n    SuperImage img = null;\r\n    SuperAnimatedImage animImg = null;\r\n    SuperImage tmpImg = null;\r\n\r\n    void finalize()\r\n    {\r\n        png.free();\r\n\r\n        if (tmpImg)\r\n        {\r\n            tmpImg.free();\r\n            tmpImg = null;\r\n        }\r\n\r\n        // don't close the stream, just release our reference\r\n        istrm = null;\r\n    }\r\n\r\n    Compound!(SuperImage, string) error(string errorMsg)\r\n    {\r\n        finalize();\r\n        if (img)\r\n        {\r\n            img.free();\r\n            img = null;\r\n        }\r\n        return compound(img, errorMsg);\r\n    }\r\n\r\n    ubyte[8] signatureBuffer;\r\n\r\n    if (!istrm.fillArray(signatureBuffer))\r\n    {\r\n        return error(\"loadPNG error: signature check failed\");\r\n    }\r\n\r\n    version(PNGDebug)\r\n    {\r\n        writeln(\"----------------\");\r\n        writeln(\"PNG Signature: \", signatureBuffer);\r\n        writeln(\"----------------\");\r\n    }\r\n\r\n    bool endChunk = false;\r\n    while (!endChunk && istrm.readable)\r\n    {\r\n        PNGChunk chunk;\r\n        auto res = readChunk(&png, istrm, &chunk);\r\n\r\n        if (!res[0])\r\n        {\r\n            chunk.free();\r\n            return error(res[1]);\r\n        }\r\n        else\r\n        {\r\n            if (chunk.type == IEND)\r\n            {\r\n                endChunk = true;\r\n                chunk.free();\r\n            }\r\n            else if (chunk.type == IHDR)\r\n            {\r\n                res = readIHDR(&png, &chunk);\r\n                chunk.free();\r\n                if (!res[0])\r\n                {\r\n                    return error(res[1]);\r\n                }\r\n\r\n                png.decodingFirstFrame = true;\r\n                png.frame.width = png.hdr.width;\r\n                png.frame.height = png.hdr.height;\r\n                png.frame.x = 0;\r\n                png.frame.y = 0;\r\n\r\n                png.initFrameBuffer();\r\n                png.initDecoder();\r\n            }\r\n            else if (chunk.type == IDAT)\r\n            {\r\n                png.decoder.decode(chunk.data);\r\n                chunk.free();\r\n            }\r\n            else if (chunk.type == PLTE)\r\n            {\r\n                png.palette = chunk.data;\r\n            }\r\n            else if (chunk.type == tRNS)\r\n            {\r\n                png.transparency = chunk.data;\r\n\r\n                if (png.hdr.colorType == ColorType.Palette)\r\n                {\r\n                    if (png.transparency.length > 0)\r\n                        png.numChannels = 4;\r\n                    else\r\n                        png.numChannels = 3;\r\n                }\r\n\r\n                version(PNGDebug)\r\n                {\r\n                    writefln(\"transparency.length = %s\", png.transparency.length);\r\n                }\r\n\r\n                png.initFrameBuffer();\r\n                png.initDecoder();\r\n            }\r\n            else if (chunk.type == oFFs)\r\n            {\r\n                png.offset.readFromBuffer(chunk.data);\r\n                chunk.free();\r\n\r\n                version(PNGDebug)\r\n                {\r\n                    writefln(\"posX = %s\", png.offset.posX);\r\n                    writefln(\"posY = %s\", png.offset.posY);\r\n                    writefln(\"unitSpecifier = %s\", png.offset.unitSpecifier);\r\n                }\r\n            }\r\n            else if (chunk.type == acTL)\r\n            {\r\n                AnimationControlChunk animControl;\r\n                animControl.readFromBuffer(chunk.data);\r\n                png.numFrames = animControl.numFrames;\r\n                png.numLoops = animControl.numPlays;\r\n                png.isAnimated = true;\r\n\r\n                version(PNGDebug)\r\n                {\r\n                    writefln(\"numFrames = %s\", png.numFrames);\r\n                    writefln(\"numLoops = %s\", png.numLoops);\r\n                }\r\n\r\n                chunk.free();\r\n            }\r\n            else if (chunk.type == fcTL)\r\n            {\r\n                png.frame.readFromBuffer(chunk.data);\r\n\r\n                version(PNGDebug)\r\n                {\r\n                    writefln(\"sequenceNumber = %s\", png.frame.sequenceNumber);\r\n                    writefln(\"frameWidth = %s\", png.frame.width);\r\n                    writefln(\"frameHeight = %s\", png.frame.height);\r\n                    writefln(\"frameX = %s\", png.frame.x);\r\n                    writefln(\"frameY = %s\", png.frame.y);\r\n                    writefln(\"delayNumerator = %s\", png.frame.delayNumerator);\r\n                    writefln(\"delayDenominator = %s\", png.frame.delayDenominator);\r\n                    writefln(\"disposeOp = %s\", cast(DisposeOp)png.frame.disposeOp);\r\n                    writefln(\"blendOp = %s\", cast(BlendOp)png.frame.blendOp);\r\n                }\r\n\r\n                png.initDecoder();\r\n\r\n                chunk.free();\r\n            }\r\n            else if (chunk.type == fdAT)\r\n            {\r\n                uint dataSequenceNumber;\r\n                *(&dataSequenceNumber) = *(cast(uint*)chunk.data.ptr);\r\n                dataSequenceNumber = bigEndian(dataSequenceNumber);\r\n                version(PNGDebug)\r\n                {\r\n                    writefln(\"sequenceNumber = %s\", dataSequenceNumber);\r\n                }\r\n\r\n                png.decoder.decode(chunk.data[4..$]);\r\n                chunk.free();\r\n            }\r\n            else\r\n            {\r\n                chunk.free();\r\n            }\r\n\r\n            version(PNGDebug)\r\n            {\r\n                writeln(\"----------------\");\r\n            }\r\n        }\r\n\r\n        if (png.decoder.hasEnded)\r\n        {\r\n            if (img is null)\r\n            {\r\n                tmpImg = imgFac.createImage(png.hdr.width, png.hdr.height, png.numChannels, png.bitDepth);\r\n                img = imgFac.createImage(png.hdr.width, png.hdr.height, png.numChannels, png.bitDepth, png.numFrames);\r\n\r\n                if (png.isAnimated)\r\n                    animImg = cast(SuperAnimatedImage)img;\r\n            }\r\n\r\n            res = fillFrame(&png);\r\n            if (res[0])\r\n            {\r\n                if (png.decodingFirstFrame)\r\n                {\r\n                    png.decodingFirstFrame = false;\r\n\r\n                    if (tmpImg.data.length != png.frameBuffer.length)\r\n                    {\r\n                        return error(\"loadPNG error: uncompressed data length mismatch\");\r\n                    }\r\n\r\n                    tmpImg.data[] = png.frameBuffer[0..png.frameSize];\r\n                    img.data[] = tmpImg.data[];\r\n\r\n                    if (animImg)\r\n                    {\r\n                        disposeFrame(&png, animImg, tmpImg, true);\r\n                    }\r\n                }\r\n                else\r\n                {\r\n                    blitFrame(&png, png.frameBuffer, tmpImg);\r\n\r\n                    img.data[] = tmpImg.data[];\r\n\r\n                    uint f = animImg.currentFrame;\r\n                    animImg.currentFrame = f - 1;\r\n                    disposeFrame(&png, animImg, tmpImg, false);\r\n                    animImg.currentFrame = f;\r\n                }\r\n\r\n                if (animImg)\r\n                {\r\n                    if (animImg.currentFrame == animImg.numFrames-1)\r\n                    {\r\n                        // Last frame, stop here\r\n                        animImg.currentFrame = 0;\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            else\r\n            {\r\n                return error(res[1]);\r\n            }\r\n\r\n            if (animImg)\r\n            {\r\n                animImg.advanceFrame();\r\n            }\r\n            else\r\n            {\r\n                // Stop decoding if we don't need animation\r\n                break;\r\n            }\r\n        }\r\n    }\r\n\r\n    finalize();\r\n    return compound(img, \"\");\r\n}\r\n\r\n/**\r\n * Load animated PNG (APNG) from stream using specified image factory.\r\n * GC-free\r\n */\r\nCompound!(SuperAnimatedImage, string) loadAPNG(\r\n    InputStream istrm,\r\n    SuperImageFactory imgFac)\r\n{\r\n    SuperAnimatedImage img = null;\r\n    auto res = loadPNG(istrm, imgFac);\r\n    if (res[0])\r\n        img = cast(SuperAnimatedImage)res[0];\r\n    return compound(img, res[1]);\r\n}\r\n\r\n/**\r\n * Save APNG to stream.\r\n * GC-free\r\n */\r\nCompound!(bool, string) saveAPNG(SuperAnimatedImage img, OutputStream output)\r\nin\r\n{\r\n    assert (img.data.length);\r\n}\r\ndo\r\n{\r\n    ubyte[] raw;\r\n    ubyte[] buffer;\r\n\r\n    void finalize()\r\n    {\r\n        if (buffer.length)\r\n            Delete(buffer);\r\n        if (raw.length)\r\n            Delete(raw);\r\n    }\r\n\r\n    Compound!(bool, string) error(string errorMsg)\r\n    {\r\n        finalize();\r\n        return compound(false, errorMsg);\r\n    }\r\n\r\n    if (img.bitDepth != 8)\r\n        return error(\"savePNG error: only 8-bit images are supported by encoder\");\r\n\r\n    bool writeChunk(ubyte[4] chunkType, ubyte[] chunkData)\r\n    {\r\n        PNGChunk hdrChunk;\r\n        hdrChunk.length = cast(uint)chunkData.length;\r\n        hdrChunk.type = chunkType;\r\n        hdrChunk.data = chunkData;\r\n        hdrChunk.crc = crc32(chain(chunkType[0..$], hdrChunk.data));\r\n\r\n        if (!output.writeBE!uint(hdrChunk.length)\r\n            || !output.writeArray(hdrChunk.type))\r\n            return false;\r\n\r\n        if (chunkData.length)\r\n            if (!output.writeArray(hdrChunk.data))\r\n                return false;\r\n\r\n        if (!output.writeBE!uint(hdrChunk.crc))\r\n            return false;\r\n\r\n        return true;\r\n    }\r\n\r\n    bool writeHeader()\r\n    {\r\n        PNGHeader hdr;\r\n        hdr.width = networkByteOrder(img.width);\r\n        hdr.height = networkByteOrder(img.height);\r\n        hdr.bitDepth = 8;\r\n        if (img.channels == 4)\r\n            hdr.colorType = ColorType.RGBA;\r\n        else if (img.channels == 3)\r\n            hdr.colorType = ColorType.RGB;\r\n        else if (img.channels == 2)\r\n            hdr.colorType = ColorType.GreyscaleAlpha;\r\n        else if (img.channels == 1)\r\n            hdr.colorType = ColorType.Greyscale;\r\n        hdr.compressionMethod = 0;\r\n        hdr.filterMethod = 0;\r\n        hdr.interlaceMethod = 0;\r\n\r\n        return writeChunk(IHDR, hdr.bytes);\r\n    }\r\n\r\n    uint seqNumber = 0;\r\n\r\n    bool writeAnimationControlChunk()\r\n    {\r\n        AnimationControlChunk actl;\r\n        actl.numFrames = networkByteOrder(img.numFrames);\r\n        actl.numPlays = networkByteOrder(0);\r\n        return writeChunk(acTL, actl.bytes);\r\n    }\r\n\r\n    bool writeFrameControlChunk()\r\n    {\r\n        FrameControlChunk fctl;\r\n        fctl.sequenceNumber = networkByteOrder(seqNumber);\r\n        seqNumber++;\r\n        fctl.width = networkByteOrder(img.width);\r\n        fctl.height = networkByteOrder(img.height);\r\n        fctl.x = networkByteOrder(0);\r\n        fctl.y = networkByteOrder(0);\r\n        // TODO: add timeStep to SuperAnimatedImage\r\n        fctl.delayNumerator = networkByteOrder(75);\r\n        fctl.delayDenominator = networkByteOrder(1000);\r\n        fctl.disposeOp = DisposeOp.Background;\r\n        fctl.blendOp = BlendOp.Source;\r\n        return writeChunk(fcTL, fctl.bytes);\r\n    }\r\n\r\n    bool writeFrameDataChunk(ubyte[] data)\r\n    {\r\n        uint len = cast(uint)data.length + 4;\r\n        ubyte[4] type = fdAT;\r\n        uint seq = seqNumber;\r\n        uint seqBE = networkByteOrder(seqNumber);\r\n        seqNumber++;\r\n        ubyte[4] seqNumberBytes;\r\n        seqNumberBytes = (cast(ubyte*)&seqBE)[0..4][];\r\n        uint crc = crc32(chain(type[0..$], seqNumberBytes[0..$], data));\r\n\r\n        if (!output.writeBE!uint(len)\r\n            || !output.writeArray(type))\r\n            return false;\r\n\r\n        if (!output.writeBE!uint(seq))\r\n            return false;\r\n\r\n        if (data.length)\r\n            if (!output.writeArray(data))\r\n                return false;\r\n\r\n        if (!output.writeBE!uint(crc))\r\n            return false;\r\n\r\n        return true;\r\n    }\r\n\r\n    output.writeArray(PNGSignature);\r\n    if (!writeHeader())\r\n        return error(\"savePNG error: write failed (disk full?)\");\r\n\r\n    if (!writeAnimationControlChunk())\r\n        return error(\"savePNG error: write failed (disk full?)\");\r\n\r\n    //TODO: filtering\r\n    raw = New!(ubyte[])(img.width * img.height * img.channels + img.height);\r\n    buffer = New!(ubyte[])(64 * 1024);\r\n\r\n    bool encode(uint frame)\r\n    {\r\n        if (!writeFrameControlChunk())\r\n            return false;\r\n\r\n        foreach(y; 0..img.height)\r\n        {\r\n            auto rowStart = y * (img.width * img.channels + 1);\r\n            raw[rowStart] = 0; // No filter\r\n\r\n            foreach(x; 0..img.width)\r\n            {\r\n                auto dataIndex = (y * img.width + x) * img.channels;\r\n                auto rawIndex = rowStart + 1 + x * img.channels;\r\n\r\n                foreach(ch; 0..img.channels)\r\n                    raw[rawIndex + ch] = img.data[dataIndex + ch];\r\n            }\r\n        }\r\n\r\n        ZlibBufferedEncoder zlibEncoder = ZlibBufferedEncoder(buffer, raw);\r\n        while (!zlibEncoder.ended)\r\n        {\r\n            auto len = zlibEncoder.encode();\r\n            if (len > 0)\r\n            {\r\n                bool res;\r\n                if (frame == 0)\r\n                    res = writeChunk(IDAT, zlibEncoder.buffer[0..len]);\r\n                else\r\n                    res = writeFrameDataChunk(zlibEncoder.buffer[0..len]);\r\n\r\n                if (!res)\r\n                    return false;\r\n            }\r\n        }\r\n\r\n        return true;\r\n    }\r\n\r\n    uint startFrame = img.currentFrame;\r\n    foreach(f; 0..img.numFrames)\r\n    {\r\n        img.currentFrame = f;\r\n        if (!encode(f))\r\n            return error(\"savePNG error: write failed (disk full?)\");\r\n    }\r\n    img.currentFrame = startFrame;\r\n\r\n    writeChunk(IEND, []);\r\n\r\n    finalize();\r\n    return compound(true, \"\");\r\n}\r\n\r\n/**\r\n * Save PNG to stream.\r\n * GC-free\r\n */\r\nCompound!(bool, string) savePNG(SuperImage img, OutputStream output)\r\nin\r\n{\r\n    assert (img.data.length);\r\n}\r\ndo\r\n{\r\n    Compound!(bool, string) error(string errorMsg)\r\n    {\r\n        return compound(false, errorMsg);\r\n    }\r\n\r\n    if (img.bitDepth != 8)\r\n        return error(\"savePNG error: only 8-bit images are supported by encoder\");\r\n\r\n    bool writeChunk(ubyte[4] chunkType, ubyte[] chunkData)\r\n    {\r\n        PNGChunk hdrChunk;\r\n        hdrChunk.length = cast(uint)chunkData.length;\r\n        hdrChunk.type = chunkType;\r\n        hdrChunk.data = chunkData;\r\n        hdrChunk.crc = crc32(chain(chunkType[0..$], hdrChunk.data));\r\n\r\n        if (!output.writeBE!uint(hdrChunk.length)\r\n            || !output.writeArray(hdrChunk.type))\r\n            return false;\r\n\r\n        if (chunkData.length)\r\n            if (!output.writeArray(hdrChunk.data))\r\n                return false;\r\n\r\n        if (!output.writeBE!uint(hdrChunk.crc))\r\n            return false;\r\n\r\n        return true;\r\n    }\r\n\r\n    bool writeHeader()\r\n    {\r\n        PNGHeader hdr;\r\n        hdr.width = networkByteOrder(img.width);\r\n        hdr.height = networkByteOrder(img.height);\r\n        hdr.bitDepth = 8;\r\n        if (img.channels == 4)\r\n            hdr.colorType = ColorType.RGBA;\r\n        else if (img.channels == 3)\r\n            hdr.colorType = ColorType.RGB;\r\n        else if (img.channels == 2)\r\n            hdr.colorType = ColorType.GreyscaleAlpha;\r\n        else if (img.channels == 1)\r\n            hdr.colorType = ColorType.Greyscale;\r\n        hdr.compressionMethod = 0;\r\n        hdr.filterMethod = 0;\r\n        hdr.interlaceMethod = 0;\r\n\r\n        return writeChunk(IHDR, hdr.bytes);\r\n    }\r\n\r\n    output.writeArray(PNGSignature);\r\n    if (!writeHeader())\r\n        return error(\"savePNG error: write failed (disk full?)\");\r\n\r\n    //TODO: filtering\r\n    ubyte[] raw = New!(ubyte[])(img.width * img.height * img.channels + img.height);\r\n    foreach(y; 0..img.height)\r\n    {\r\n        auto rowStart = y * (img.width * img.channels + 1);\r\n        raw[rowStart] = 0; // No filter\r\n\r\n        foreach(x; 0..img.width)\r\n        {\r\n            auto dataIndex = (y * img.width + x) * img.channels;\r\n            auto rawIndex = rowStart + 1 + x * img.channels;\r\n\r\n            foreach(ch; 0..img.channels)\r\n                raw[rawIndex + ch] = img.data[dataIndex + ch];\r\n        }\r\n    }\r\n\r\n    ubyte[] buffer = New!(ubyte[])(64 * 1024);\r\n    ZlibBufferedEncoder zlibEncoder = ZlibBufferedEncoder(buffer, raw);\r\n    while (!zlibEncoder.ended)\r\n    {\r\n        auto len = zlibEncoder.encode();\r\n        if (len > 0)\r\n            writeChunk(IDAT, zlibEncoder.buffer[0..len]);\r\n    }\r\n\r\n    writeChunk(IEND, []);\r\n\r\n    Delete(buffer);\r\n    Delete(raw);\r\n\r\n    return compound(true, \"\");\r\n}\r\n\r\nCompound!(bool, string) err(string msg)\r\n{\r\n    return compound(false, msg);\r\n}\r\n\r\nCompound!(bool, string) suc()\r\n{\r\n    return compound(true, \"\");\r\n}\r\n\r\nCompound!(bool, string) readChunk(\r\n    PNGImage* png,\r\n    InputStream istrm,\r\n    PNGChunk* chunk)\r\n{\r\n    if (!istrm.readBE!uint(&chunk.length) ||\r\n        !istrm.fillArray(chunk.type))\r\n    {\r\n        return err(\"loadPNG error: failed to read chunk, invalid PNG stream\");\r\n    }\r\n\r\n    version(PNGDebug) writefln(\"Chunk length = %s\", chunk.length);\r\n    version(PNGDebug) writefln(\"Chunk type = %s\", cast(char[])chunk.type);\r\n\r\n    if (chunk.length > 0)\r\n    {\r\n        chunk.data = New!(ubyte[])(chunk.length);\r\n        if (!istrm.fillArray(chunk.data))\r\n        {\r\n            return err(\"loadPNG error: failed to read chunk data, invalid PNG stream\");\r\n        }\r\n    }\r\n\r\n    version(PNGDebug) writefln(\"Chunk data.length = %s\", chunk.data.length);\r\n\r\n    if (!istrm.readBE!uint(&chunk.crc))\r\n    {\r\n        return err(\"loadPNG error: failed to read chunk CRC, invalid PNG stream\");\r\n    }\r\n\r\n    /*\r\n    uint calculatedCRC = crc32(chain(chunk.type[0..$], chunk.data));\r\n\r\n    version(PNGDebug)\r\n    {\r\n        writefln(\"Chunk CRC = %X\", chunk.crc);\r\n        writefln(\"Calculated CRC = %X\", calculatedCRC);\r\n    }\r\n\r\n    if (chunk.crc != calculatedCRC)\r\n    {\r\n        return err(\"loadPNG error: chunk CRC check failed\");\r\n    }\r\n    */\r\n\r\n    return suc();\r\n}\r\n\r\nCompound!(bool, string) readIHDR(\r\n    PNGImage* png,\r\n    PNGChunk* chunk)\r\n{\r\n    PNGHeader* hdr = &png.hdr;\r\n\r\n    if (chunk.data.length < hdr.bytes.length)\r\n        return err(\"loadPNG error: illegal header chunk\");\r\n\r\n    hdr.bytes[] = chunk.data[0..hdr.bytes.length];\r\n    hdr.width = bigEndian(hdr.width);\r\n    hdr.height = bigEndian(hdr.height);\r\n\r\n    version(PNGDebug)\r\n    {\r\n        writefln(\"width = %s\", hdr.width);\r\n        writefln(\"height = %s\", hdr.height);\r\n        writefln(\"bitDepth = %s\", hdr.bitDepth);\r\n        writefln(\"colorType = %s\", hdr.colorType);\r\n        writefln(\"compressionMethod = %s\", hdr.compressionMethod);\r\n        writefln(\"filterMethod = %s\", hdr.filterMethod);\r\n        writefln(\"interlaceMethod = %s\", hdr.interlaceMethod);\r\n    }\r\n\r\n    bool supportedIndexed =\r\n        (hdr.colorType == ColorType.Palette) &&\r\n        (hdr.bitDepth == 1 ||\r\n         hdr.bitDepth == 2 ||\r\n         hdr.bitDepth == 4 ||\r\n         hdr.bitDepth == 8);\r\n\r\n    if (hdr.bitDepth != 8 && hdr.bitDepth != 16 && !supportedIndexed)\r\n        return err(\"loadPNG error: unsupported bit depth\");\r\n\r\n    if (hdr.compressionMethod != 0)\r\n        return err(\"loadPNG error: unsupported compression method\");\r\n\r\n    if (hdr.filterMethod != 0)\r\n        return err(\"loadPNG error: unsupported filter method\");\r\n\r\n    if (hdr.interlaceMethod != 0)\r\n        return err(\"loadPNG error: interlacing is not supported\");\r\n\r\n    if (hdr.colorType == ColorType.Greyscale)\r\n        png.numChannels = 1;\r\n    else if (hdr.colorType == ColorType.GreyscaleAlpha)\r\n        png.numChannels = 2;\r\n    else if (hdr.colorType == ColorType.RGB)\r\n        png.numChannels = 3;\r\n    else if (hdr.colorType == ColorType.RGBA)\r\n        png.numChannels = 4;\r\n    else if (hdr.colorType == ColorType.Palette)\r\n    {\r\n        if (png.transparency.length > 0)\r\n            png.numChannels = 4;\r\n        else\r\n            png.numChannels = 3;\r\n    }\r\n    else\r\n        return err(\"loadPNG error: unsupported color type\");\r\n\r\n    if (hdr.colorType == ColorType.Palette)\r\n        png.bitDepth = 8;\r\n    else\r\n        png.bitDepth = hdr.bitDepth;\r\n\r\n    png.bytesPerChannel = png.bitDepth / 8;\r\n\r\n    version(PNGDebug)\r\n    {\r\n        writefln(\"bytesPerChannel = %s\", png.bytesPerChannel);\r\n    }\r\n\r\n    return suc();\r\n}\r\n\r\nCompound!(bool, string) fillFrame(PNGImage* png)\r\n{\r\n    ubyte[] decodedBuffer = png.decoder.buffer;\r\n    version(PNGDebug) writefln(\"decodedBuffer.length = %s\", decodedBuffer.length);\r\n\r\n    bool indexed = (png.hdr.colorType == ColorType.Palette);\r\n\r\n    uint calculatedSize;\r\n    if (indexed)\r\n        calculatedSize = (png.frame.width * png.frame.height * png.hdr.bitDepth) / 8 + png.frame.height;\r\n    else\r\n        calculatedSize = png.frame.width * png.frame.height * (png.hdr.bitDepth / 8) * png.numChannels + png.frame.height;\r\n\r\n    png.frameSize = png.frame.width * png.frame.height * png.numChannels * png.bytesPerChannel;\r\n    png.filteredBufferSize = calculatedSize - png.frame.height;\r\n\r\n    version(PNGDebug)\r\n    {\r\n        writefln(\"calculatedSize = %s\", calculatedSize);\r\n        writefln(\"frameSize = %s\", png.frameSize);\r\n        writefln(\"filteredBufferSize = %s\", png.filteredBufferSize);\r\n\r\n        writefln(\"png.frameBuffer.length = %s\", png.frameBuffer.length);\r\n    }\r\n\r\n    ubyte[] pdata = png.frameBuffer[0..png.frameSize];\r\n    ubyte[] filteredBuffer = png.filteredBuffer[0..png.filteredBufferSize];\r\n\r\n    if (decodedBuffer.length != calculatedSize)\r\n    {\r\n        return err(\"loadPNG error: image size and data mismatch\");\r\n    }\r\n\r\n    // apply filtering to the image data\r\n    auto res = filter(png, indexed, decodedBuffer, filteredBuffer);\r\n    if (!res[0])\r\n    {\r\n        return err(res[1]);\r\n    }\r\n\r\n    // if a palette is used, substitute target colors\r\n    if (indexed)\r\n    {\r\n        if (png.palette.length == 0)\r\n            return err(\"loadPNG error: palette chunk not found\");\r\n\r\n        if (png.hdr.bitDepth == 8)\r\n        {\r\n            for (int i = 0; i < filteredBuffer.length; ++i)\r\n            {\r\n                ubyte b = filteredBuffer[i];\r\n                pdata[i * png.numChannels + 0] = png.palette[b * 3 + 0];\r\n                pdata[i * png.numChannels + 1] = png.palette[b * 3 + 1];\r\n                pdata[i * png.numChannels + 2] = png.palette[b * 3 + 2];\r\n                if (png.transparency.length > 0)\r\n                    pdata[i * png.numChannels + 3] =\r\n                        b < png.transparency.length ? png.transparency[b] : 255;\r\n            }\r\n        }\r\n        else // bit depths 1, 2, 4\r\n        {\r\n            int srcindex = 0;\r\n            int srcshift = 8 - png.hdr.bitDepth;\r\n            ubyte mask = cast(ubyte)((1 << png.hdr.bitDepth) - 1);\r\n            int sz = png.frame.width * png.frame.height;\r\n            for (int dstindex = 0; dstindex < sz; dstindex++)\r\n            {\r\n                auto b = ((filteredBuffer[srcindex] >> srcshift) & mask);\r\n                pdata[dstindex * png.numChannels + 0] = png.palette[b * 3 + 0];\r\n                pdata[dstindex * png.numChannels + 1] = png.palette[b * 3 + 1];\r\n                pdata[dstindex * png.numChannels + 2] = png.palette[b * 3 + 2];\r\n\r\n                if (png.transparency.length > 0)\r\n                    pdata[dstindex * png.numChannels + 3] =\r\n                        b < png.transparency.length ? png.transparency[b] : 255;\r\n\r\n                if (srcshift <= 0)\r\n                {\r\n                    srcshift = 8 - png.hdr.bitDepth;\r\n                    srcindex++;\r\n                }\r\n                else\r\n                {\r\n                    srcshift -= png.hdr.bitDepth;\r\n                }\r\n            }\r\n        }\r\n    }\r\n    else\r\n    {\r\n        pdata[] = filteredBuffer[];\r\n    }\r\n\r\n    return suc();\r\n}\r\n\r\nvoid blitFrame(\r\n    PNGImage* png,\r\n    ubyte[] frameBuffer,\r\n    SuperImage img)\r\n{\r\n    for(uint y = 0; y < png.frame.height; y++)\r\n    {\r\n        for(uint x = 0; x < png.frame.width; x++)\r\n        {\r\n            Color4f c1 = img[png.frame.x + x, png.frame.y + y];\r\n            Color4f c2 = getColor(png, frameBuffer, x, y);\r\n\r\n            if (png.frame.blendOp == BlendOp.Source)\r\n                img[png.frame.x + x, png.frame.y + y] = c2;\r\n            else\r\n                img[png.frame.x + x, png.frame.y + y] = alphaOver(c1, c2);\r\n        }\r\n    }\r\n}\r\n\r\nvoid disposeFrame(\r\n    PNGImage* png,\r\n    SuperImage prevImg,\r\n    SuperImage img,\r\n    bool firstFrame)\r\n{\r\n    if (png.frame.disposeOp != DisposeOp.None)\r\n    for(uint y = 0; y < png.hdr.height; y++)\r\n    {\r\n        for(uint x = 0; x < png.hdr.width; x++)\r\n        {\r\n            if (png.frame.disposeOp == DisposeOp.Previous && !firstFrame)\r\n                img[x, y] = prevImg[x, y];\r\n            else\r\n                img[x, y] = Color4f(0, 0, 0, 0);\r\n        }\r\n    }\r\n}\r\n\r\nColor4f getColor(\r\n    PNGImage* png,\r\n    ubyte[] pixData,\r\n    uint x,\r\n    uint y)\r\n{\r\n    uint bitDepth = png.bitDepth;\r\n    uint channels = png.numChannels;\r\n    uint pixelSize = png.bytesPerChannel * channels;\r\n    uint index = (y * png.frame.width + x) * pixelSize;\r\n    uint maxv = (2 ^^ bitDepth) - 1;\r\n\r\n    Color4 res = Color4(0, 0, 0, 0);\r\n\r\n    if (channels == 1 && bitDepth == 8)\r\n    {\r\n        auto v = pixData[index];\r\n        res = Color4(v, v, v);\r\n    }\r\n    else if (channels == 2 && bitDepth == 8)\r\n    {\r\n        auto v = pixData[index];\r\n        res = Color4(v, v, v, pixData[index+1]);\r\n    }\r\n    else if (channels == 3 && bitDepth == 8)\r\n    {\r\n        res = Color4(pixData[index], pixData[index+1], pixData[index+2], cast(ubyte)maxv);\r\n    }\r\n    else if (channels == 4 && bitDepth == 8)\r\n    {\r\n        res = Color4(pixData[index], pixData[index+1], pixData[index+2], pixData[index+3]);\r\n    }\r\n    else if (channels == 1 && bitDepth == 16)\r\n    {\r\n        ushort v = pixData[index] << 8 | pixData[index+1];\r\n        res = Color4(v, v, v);\r\n    }\r\n    else if (channels == 2 && bitDepth == 16)\r\n    {\r\n        ushort v = pixData[index]   << 8 | pixData[index+1];\r\n        ushort a = pixData[index+2] << 8 | pixData[index+3];\r\n        res = Color4(v, v, v, a);\r\n    }\r\n    else if (channels == 3 && bitDepth == 16)\r\n    {\r\n        ushort r = pixData[index]   << 8 | pixData[index+1];\r\n        ushort g = pixData[index+2] << 8 | pixData[index+3];\r\n        ushort b = pixData[index+4] << 8 | pixData[index+5];\r\n        ushort a = cast(ushort)maxv;\r\n        res = Color4(r, g, b, a);\r\n    }\r\n    else if (channels == 4 && bitDepth == 16)\r\n    {\r\n        ushort r = pixData[index]   << 8 | pixData[index+1];\r\n        ushort g = pixData[index+2] << 8 | pixData[index+3];\r\n        ushort b = pixData[index+4] << 8 | pixData[index+5];\r\n        ushort a = pixData[index+6] << 8 | pixData[index+7];\r\n        res = Color4(r, g, b, a);\r\n    }\r\n    else\r\n        assert(0);\r\n\r\n    return Color4f(res, bitDepth);\r\n}\r\n\r\n/*\r\n * Performs the paeth PNG filter from pixels values:\r\n *   a = back\r\n *   b = up\r\n *   c = up and back\r\n */\r\npure ubyte paeth(ubyte a, ubyte b, ubyte c)\r\n{\r\n    int p = a + b - c;\r\n    int pa = std.math.abs(p - a);\r\n    int pb = std.math.abs(p - b);\r\n    int pc = std.math.abs(p - c);\r\n    if (pa <= pb && pa <= pc) return a;\r\n    else if (pb <= pc) return b;\r\n    else return c;\r\n}\r\n\r\nCompound!(bool, string) filter(\r\n      PNGImage* png,\r\n      bool indexed,\r\n      ubyte[] ibuffer,\r\n      ubyte[] obuffer)\r\n{\r\n    uint width = png.frame.width;\r\n    uint height = png.frame.height;\r\n    uint channels = png.numChannels;\r\n\r\n    uint bytesPerPixel = png.hdr.bitDepth / 8; // 1 for 8bit, 2 for 16bit\r\n\r\n    uint scanlineSize;\r\n    if (indexed)\r\n        scanlineSize = (width * png.hdr.bitDepth) / 8 + 1;\r\n    else\r\n        scanlineSize = width * bytesPerPixel * channels + 1;\r\n\r\n    ubyte pback, pup, pupback, cbyte;\r\n\r\n    for (int i = 0; i < height; ++i)\r\n    {\r\n        pback = 0;\r\n\r\n        // get the first byte of a scanline\r\n        ubyte scanFilter = ibuffer[i * scanlineSize];\r\n\r\n        if (indexed)\r\n        {\r\n            width = scanlineSize - 1;\r\n            for (int j = 0; j < width; ++j)\r\n            {\r\n                if (i == 0) pup = 0;\r\n                else pup = obuffer[(i-1) * width + j];\r\n                if (j == 0) pback = 0;\r\n                else pback = obuffer[i * width + j-1];\r\n                if (i == 0 || j == 0) pupback = 0;\r\n                else pupback = obuffer[(i-1) * width + j-1];\r\n\r\n                cbyte = ibuffer[i * scanlineSize + j+1];\r\n\r\n                // filter, then set the current byte in data\r\n                switch (scanFilter)\r\n                {\r\n                    case FilterMethod.None:\r\n                        obuffer[i * width + j] = cbyte;\r\n                        break;\r\n                    case FilterMethod.Sub:\r\n                        obuffer[i * width + j] = cast(ubyte)(cbyte + pback);\r\n                        break;\r\n                    case FilterMethod.Up:\r\n                        obuffer[i * width + j] = cast(ubyte)(cbyte + pup);\r\n                        break;\r\n                    case FilterMethod.Average:\r\n                        obuffer[i * width + j] = cast(ubyte)(cbyte + (pback + pup) / 2);\r\n                        break;\r\n                    case FilterMethod.Paeth:\r\n                        obuffer[i * width + j] = cast(ubyte)(cbyte + paeth(pback, pup, pupback));\r\n                        break;\r\n                    default:\r\n                        return err(format(\"loadPNG error: unknown scanline filter (%s)\", scanFilter));\r\n                }\r\n            }\r\n        }\r\n        else\r\n        {\r\n            for (int j = 0; j < width; ++j)\r\n            {\r\n                for (int k = 0; k < bytesPerPixel * channels; ++k)\r\n                {\r\n                    if (i == 0) pup = 0;\r\n                    else pup = obuffer[((i-1) * width + j) * bytesPerPixel * channels + k];\r\n                    if (j == 0) pback = 0;\r\n                    else pback = obuffer[(i * width + j-1) * bytesPerPixel * channels + k];\r\n                    if (i == 0 || j == 0) pupback = 0;\r\n                    else pupback = obuffer[((i-1) * width + j-1) * bytesPerPixel * channels + k];\r\n\r\n                    // get the current byte from ibuffer\r\n                    cbyte = ibuffer[i * (width * bytesPerPixel * channels + 1) + j * bytesPerPixel * channels + k + 1];\r\n\r\n                    // filter, then set the current byte in data\r\n                    switch (scanFilter)\r\n                    {\r\n                        case FilterMethod.None:\r\n                            obuffer[(i * width + j) * bytesPerPixel * channels + k] = cbyte;\r\n                            break;\r\n                        case FilterMethod.Sub:\r\n                            obuffer[(i * width + j) * bytesPerPixel * channels + k] = cast(ubyte)(cbyte + pback);\r\n                            break;\r\n                        case FilterMethod.Up:\r\n                            obuffer[(i * width + j) * bytesPerPixel * channels + k] = cast(ubyte)(cbyte + pup);\r\n                            break;\r\n                        case FilterMethod.Average:\r\n                            obuffer[(i * width + j) * bytesPerPixel * channels + k] = cast(ubyte)(cbyte + (pback + pup) / 2);\r\n                            break;\r\n                        case FilterMethod.Paeth:\r\n                            obuffer[(i * width + j) * bytesPerPixel * channels + k] = cast(ubyte)(cbyte + paeth(pback, pup, pupback));\r\n                            break;\r\n                        default:\r\n                            return err(format(\"loadPNG error: unknown scanline filter (%s)\", scanFilter));\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    return suc();\r\n}\r\n\r\nuint crc32(R)(R range, uint inCrc = 0) if (isInputRange!R)\r\n{\r\n    uint[256] generateTable()\r\n    {\r\n        uint[256] table;\r\n        uint crc;\r\n        for (int i = 0; i < 256; i++)\r\n        {\r\n            crc = i;\r\n            for (int j = 0; j < 8; j++)\r\n                crc = crc & 1 ? (crc >> 1) ^ 0xEDB88320UL : crc >> 1;\r\n            table[i] = crc;\r\n        }\r\n        return table;\r\n    }\r\n\r\n    static const uint[256] table = generateTable();\r\n\r\n    uint crc;\r\n\r\n    crc = inCrc ^ 0xFFFFFFFF;\r\n    foreach(v; range)\r\n        crc = (crc >> 8) ^ table[(crc ^ v) & 0xFF];\r\n\r\n    return (crc ^ 0xFFFFFFFF);\r\n}\r\n"
  },
  {
    "path": "dlib/image/io/tga.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Timur Gafarov, Roman Chistokhodov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Decode JPEG images\r\n *\r\n * Copyright: Timur Gafarov, Roman Chistokhodov 2014-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov, Roman Chistokhodov\r\n */\r\nmodule dlib.image.io.tga;\r\n\r\nimport std.stdio;\r\nimport std.file;\r\nimport std.conv;\r\nimport dlib.core.memory;\r\nimport dlib.core.stream;\r\nimport dlib.core.compound;\r\nimport dlib.image.color;\r\nimport dlib.image.image;\r\nimport dlib.image.io;\r\nimport dlib.image.io.utils;\r\nimport dlib.filesystem.local;\r\n\r\n// uncomment this to see debug messages:\r\n//version = TGADebug;\r\n\r\nstruct TGAHeader\r\n{\r\n    ubyte idLength;\r\n    ubyte type;\r\n    ubyte encoding;\r\n    short colmapStart;\r\n    short colmapLen;\r\n    ubyte colmapBits;\r\n    short xstart;\r\n    short ystart;\r\n    short width;\r\n    short height;\r\n    ubyte bpp;\r\n    ubyte descriptor;\r\n}\r\n\r\nenum TGAEncoding : ubyte\r\n{\r\n    Indexed = 1,\r\n    RGB = 2,\r\n    Grey = 3,\r\n    RLE_Indexed = 9,\r\n    RLE_RGB = 10,\r\n    RLE_Grey = 11\r\n};\r\n\r\nenum TgaOrigin : ubyte\r\n{\r\n    Left = 0x00,\r\n    Right = 0x10,\r\n    Lower = 0x00,\r\n    Upper = 0x20\r\n}\r\n\r\nclass TGALoadException: ImageLoadException\r\n{\r\n    this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)\r\n    {\r\n        super(msg, file, line, next);\r\n    }\r\n}\r\n\r\n/**\r\n * Load PNG from file using local FileSystem.\r\n * Causes GC allocation\r\n */\r\nSuperImage loadTGA(string filename)\r\n{\r\n    InputStream input = openForInput(filename);\r\n\r\n    try\r\n    {\r\n        ubyte[] data = New!(ubyte[])(cast(size_t)input.size);\r\n        input.fillArray(data);\r\n        ArrayStream arrStrm = New!ArrayStream(data);\r\n        auto img = loadTGA(arrStrm);\r\n        Delete(arrStrm);\r\n        Delete(data);\r\n        return img;\r\n    }\r\n    catch (TGALoadException ex)\r\n    {\r\n        throw new Exception(\"'\" ~ filename ~ \"' :\" ~ ex.msg, ex.file, ex.line, ex.next);\r\n    }\r\n    finally\r\n    {\r\n        input.close();\r\n    }\r\n}\r\n\r\n/**\r\n * Load TGA from stream using default image factory.\r\n * Causes GC allocation\r\n */\r\nSuperImage loadTGA(InputStream istrm)\r\n{\r\n    Compound!(SuperImage, string) res =\r\n        loadTGA(istrm, defaultImageFactory);\r\n    if (res[0] is null)\r\n        throw new TGALoadException(res[1]);\r\n    else\r\n        return res[0];\r\n}\r\n\r\n/**\r\n * Load TGA from stream using specified image factory.\r\n * GC-free\r\n */\r\nCompound!(SuperImage, string) loadTGA(\r\n    InputStream istrm,\r\n    SuperImageFactory imgFac)\r\n{\r\n    SuperImage img = null;\r\n\r\n    Compound!(SuperImage, string) error(string errorMsg)\r\n    {\r\n        if (img)\r\n        {\r\n            img.free();\r\n            img = null;\r\n        }\r\n        return compound(img, errorMsg);\r\n    }\r\n\r\n    TGAHeader readHeader()\r\n    {\r\n        TGAHeader hdr = readStruct!TGAHeader(istrm);\r\n        version(TGADebug)\r\n        {\r\n            writefln(\"idLength = %s\", hdr.idLength);\r\n            writefln(\"type = %s\", hdr.type);\r\n           /*\r\n            * Encoding flag:\r\n            * 1 = Raw indexed image\r\n            * 2 = Raw RGB\r\n            * 3 = Raw greyscale\r\n            * 9 = RLE indexed\r\n            * 10 = RLE RGB\r\n            * 11 = RLE greyscale\r\n            * 32 & 33 = Other compression, indexed\r\n            */\r\n            writefln(\"encoding = %s\", hdr.encoding);\r\n\r\n            writefln(\"colmapStart = %s\", hdr.colmapStart);\r\n            writefln(\"colmapLen = %s\", hdr.colmapLen);\r\n            writefln(\"colmapBits = %s\", hdr.colmapBits);\r\n            writefln(\"xstart = %s\", hdr.xstart);\r\n            writefln(\"ystart = %s\", hdr.ystart);\r\n            writefln(\"width = %s\", hdr.width);\r\n            writefln(\"height = %s\", hdr.height);\r\n            writefln(\"bpp = %s\", hdr.bpp);\r\n            writefln(\"descriptor = %s\", hdr.descriptor);\r\n            writeln(\"-------------------\");\r\n        }\r\n        return hdr;\r\n    }\r\n\r\n    SuperImage readRawRGB(ref TGAHeader hdr)\r\n    {\r\n        uint channels = hdr.bpp / 8;\r\n        SuperImage res = imgFac.createImage(hdr.width, hdr.height, channels, 8);\r\n\r\n        if (hdr.descriptor & TgaOrigin.Upper)\r\n        {\r\n            istrm.fillArray(res.data);\r\n        }\r\n        else\r\n        {\r\n            foreach(i; 0..hdr.height)\r\n            {\r\n                istrm.fillArray(res.data[channels * hdr.width * (hdr.height-i-1)..channels * hdr.width * (hdr.height-i)]);\r\n            }\r\n        }\r\n\r\n        const ubyte alphaBits = cast(ubyte)(hdr.descriptor & 0xf);\r\n        version(TGADebug) writefln(\"Alpha bits: %s\", alphaBits);\r\n\r\n        if (channels == 4)\r\n        {\r\n            for (size_t i=0; i<res.data.length; i += channels)\r\n            {\r\n                auto alphaIndex = i+3;\r\n                res.data[alphaIndex] = cast(ubyte)((res.data[alphaIndex] & ((1 << alphaBits)-1 )) << (8 - alphaBits));\r\n            }\r\n        }\r\n\r\n        return res;\r\n    }\r\n\r\n    SuperImage readRLERGB(ref TGAHeader hdr)\r\n    {\r\n        uint channels = hdr.bpp / 8;\r\n        uint imageSize = hdr.width * hdr.height * channels;\r\n        SuperImage res = imgFac.createImage(hdr.width, hdr.height, channels, 8);\r\n\r\n        // Calculate offset to image data\r\n        uint dataOffset = 18 + hdr.idLength;\r\n\r\n        // Add palette offset for indexed images\r\n        if (hdr.type == 1)\r\n            dataOffset += 768;\r\n\r\n        // Read compressed data\r\n        // TODO: take scanline order into account (bottom-up or top-down)\r\n        ubyte[] data = New!(ubyte[])(cast(uint)istrm.size - dataOffset);\r\n        istrm.fillArray(data);\r\n\r\n        uint ii = 0;\r\n        uint i = 0;\r\n        while (ii < imageSize)\r\n        {\r\n            ubyte b = data[i];\r\n            i++;\r\n\r\n            if (b & 0x80) // Run length chunk\r\n            {\r\n                // Get run length\r\n                uint runLength = b - 127;\r\n\r\n                // Repeat the next pixel runLength times\r\n                for (uint j = 0; j < runLength; j++)\r\n                {\r\n                    foreach(pIndex; 0..channels)\r\n                        res.data[ii + pIndex] = data[i + pIndex];\r\n\r\n                    ii += channels;\r\n                }\r\n\r\n                i += channels;\r\n            }\r\n            else // Raw chunk\r\n            {\r\n                // Get run length\r\n                uint runLength = b + 1;\r\n\r\n                // Write the next runLength pixels directly\r\n                for (uint j = 0; j < runLength; j++)\r\n                {\r\n                    foreach(pIndex; 0..channels)\r\n                        res.data[ii + pIndex] = data[i + pIndex];\r\n\r\n                    ii += channels;\r\n                    i += channels;\r\n                }\r\n            }\r\n        }\r\n\r\n        Delete(data);\r\n\r\n        return res;\r\n    }\r\n\r\n    auto hdr = readHeader();\r\n\r\n    if (hdr.idLength)\r\n    {\r\n        char[] id = New!(char[])(hdr.idLength);\r\n        istrm.fillArray(id);\r\n\r\n        version(TGADebug)\r\n        {\r\n            writefln(\"id = %s\", id);\r\n        }\r\n\r\n        Delete(id);\r\n    }\r\n\r\n    if (hdr.encoding == TGAEncoding.RGB)\r\n        img = readRawRGB(hdr);\r\n    else if (hdr.encoding == TGAEncoding.RLE_RGB)\r\n        img = readRLERGB(hdr);\r\n    else\r\n        return error(\"loadTGA error: only RGB images are supported\");\r\n\r\n    img.swapRGB();\r\n\r\n    return compound(img, \"\");\r\n}\r\n\r\nvoid swapRGB(SuperImage img)\r\n{\r\n    foreach(x; 0..img.width)\r\n    {\r\n        foreach(y; 0..img.height)\r\n        {\r\n            img[x, y] = Color4f(img[x, y].bgra);\r\n        }\r\n    }\r\n}\r\n\r\n/**\r\n * Save TGA to file using local FileSystem.\r\n * Causes GC allocation\r\n */\r\nvoid saveTGA(SuperImage img, string filename)\r\n{\r\n    OutputStream output = openForOutput(filename);\r\n    Compound!(bool, string) res = saveTGA(img, output);\r\n    output.close();\r\n\r\n    if (!res[0])\r\n        throw new TGALoadException(res[1]);\r\n}\r\n\r\n/**\r\n * Save TGA to stream.\r\n * GC-free\r\n */\r\nCompound!(bool, string) saveTGA(SuperImage img, OutputStream output)\r\n{\r\n    Compound!(bool, string) error(string errorMsg)\r\n    {\r\n        return compound(false, errorMsg);\r\n    }\r\n\r\n    enum ubyte[12] tgaStart = [0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0];\r\n    output.writeArray(tgaStart);\r\n    output.writeLE(cast(ushort)img.width);\r\n    output.writeLE(cast(ushort)img.height);\r\n\r\n    const bool hasAlpha = img.channels == 4;\r\n\r\n    if (img.channels == 3)\r\n    {\r\n        output.writeLE(cast(ubyte)24);\r\n        output.writeLE(cast(ubyte)TgaOrigin.Upper);\r\n    }\r\n    else if (img.channels == 4)\r\n    {\r\n        output.writeLE(cast(ubyte)32);\r\n        output.writeLE(cast(ubyte)0x28);\r\n    }\r\n    else\r\n        return error(\"saveTGA error: unsupported number of channels\");\r\n\r\n    foreach(y; 0..img.height)\r\n    {\r\n        foreach(x; 0..img.width)\r\n        {\r\n            ubyte[4] rgb;\r\n            ColorRGBA color = img[x, y].convert(8);\r\n            rgb[0] = cast(ubyte)color[2];\r\n            rgb[1] = cast(ubyte)color[1];\r\n            rgb[2] = cast(ubyte)color[0];\r\n\r\n            if (hasAlpha)\r\n            {\r\n                rgb[3] = cast(ubyte)(color[3]);\r\n                output.writeArray(rgb[]);\r\n            }\r\n            else\r\n            {\r\n                output.writeArray(rgb[0..3]);\r\n            }\r\n        }\r\n    }\r\n\r\n    return compound(true, \"\");\r\n}\r\n"
  },
  {
    "path": "dlib/image/io/utils.d",
    "content": "/*\r\nCopyright (c) 2014-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Image I/O utility functions\r\n *\r\n * Copyright: Timur Gafarov, Roman Chistokhodov 2014-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov, Roman Chistokhodov\r\n */\r\nmodule dlib.image.io.utils;\r\n\r\nimport std.stdio;\r\nimport std.conv;\r\nimport dlib.core.stream;\r\n\r\nT readStruct(T)(File* f, bool bigEndian = false)\r\n{\r\n    T res;\r\n    foreach(ref field; res.tupleof)\r\n    {\r\n        alias FieldType = typeof(field);\r\n\r\n        ubyte[FieldType.sizeof] bytes;\r\n        f.rawRead(bytes);\r\n\r\n        FieldType val = *cast(FieldType*)bytes.ptr;\r\n\r\n        // TODO:\r\n        //if (bigEndian)\r\n        //    val = val.bigEndianToNative;\r\n\r\n        field = val;\r\n    }\r\n    return res;\r\n}\r\n\r\nT readStruct(T)(InputStream input, bool bigEndian = false)\r\n{\r\n    T res;\r\n    foreach(ref field; res.tupleof)\r\n    {\r\n        alias FieldType = typeof(field);\r\n\r\n        ubyte[FieldType.sizeof] bytes;\r\n        input.fillArray(bytes);\r\n\r\n        FieldType val = *cast(FieldType*)bytes.ptr;\r\n\r\n        // TODO:\r\n        //if (bigEndian)\r\n        //    val = val.bigEndianToNative;\r\n\r\n        field = val;\r\n    }\r\n    return res;\r\n}\r\n"
  },
  {
    "path": "dlib/image/package.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Image processing\r\n *\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image;\r\n\r\npublic\r\n{\r\n    import dlib.image.animation;\r\n    import dlib.image.arithmetics;\r\n    import dlib.image.canvas;\r\n    import dlib.image.color;\r\n    import dlib.image.fthread;\r\n    import dlib.image.hdri;\r\n    import dlib.image.hsv;\r\n    import dlib.image.image;\r\n    import dlib.image.signal2d;\r\n    import dlib.image.unmanaged;\r\n    import dlib.image.transform;\r\n    import dlib.image.filters;\r\n    import dlib.image.io;\r\n    import dlib.image.render;\r\n    import dlib.image.resampling;\r\n}\r\n"
  },
  {
    "path": "dlib/image/render/cosplasma.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Render cosine plasma pattern\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.render.cosplasma;\r\n\r\nprivate\r\n{\r\n    import std.math;\r\n    import dlib.image.image;\r\n    import dlib.image.color;\r\n}\r\n\r\n/// Render cosine plasma pattern\r\nSuperImage renderCosPlasma(SuperImage img, float factor)\r\nin\r\n{\r\n    assert (img.data.length);\r\n}\r\ndo\r\n{\r\n    foreach (y; 0..img.height)\r\n    foreach (x; 0..img.width)\r\n    {\r\n        float value = 0.5f + 0.25f * cos(x * factor) + 0.25f * cos(y * factor);\r\n        img[x, y] = Color4f(value, value, value);\r\n    }\r\n\r\n    return img;\r\n}\r\n"
  },
  {
    "path": "dlib/image/render/package.d",
    "content": "/*\nCopyright (c) 2022-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Image synthesis tools\n *\n * Copyright: Timur Gafarov 2022-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.image.render;\n\npublic\n{\n    import dlib.image.render.cosplasma;\n    import dlib.image.render.shapes;\n    import dlib.image.render.text;\n}\n"
  },
  {
    "path": "dlib/image/render/shapes.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Oleg Baharev, Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Render simple geometric shapes\r\n *\r\n * Copyright: Oleg Baharev, Timur Gafarov 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Oleg Baharev, Timur Gafarov\r\n */\r\nmodule dlib.image.render.shapes;\r\n\r\nimport std.math;\r\nimport dlib.image.image;\r\nimport dlib.image.color;\r\n\r\nenum Black = Color4f(0, 0, 0, 1);\r\nenum White = Color4f(1, 1, 1, 1);\r\n\r\n/// Fill image with solid color\r\nvoid fillColor(SuperImage simg, Color4f col)\r\n{\r\n    foreach(y; simg.col)\r\n    foreach(x; simg.row)\r\n        simg[x, y] = col;\r\n}\r\n\r\n/// Draw a line segment\r\nvoid drawLine(SuperImage img, Color4f color, int x1, int y1, int x2, int y2)\r\n{\r\n    int dx = x2 - x1;\r\n    int ix = (dx > 0) - (dx < 0);\r\n    int dx2 = abs(dx) * 2;\r\n    int dy = y2 - y1;\r\n    int iy = (dy > 0) - (dy < 0);\r\n    int dy2 = abs(dy) * 2;\r\n    img[x1, y1] = color;\r\n\r\n    if (dx2 >= dy2)\r\n    {\r\n        int error = dy2 - (dx2 / 2);\r\n        while (x1 != x2)\r\n        {\r\n            if (error >= 0 && (error || (ix > 0)))\r\n            {\r\n                error -= dx2;\r\n                y1 += iy;\r\n            }\r\n\r\n            error += dy2;\r\n            x1 += ix;\r\n            img[x1, y1] = color;\r\n        }\r\n    }\r\n    else\r\n    {\r\n        int error = dx2 - (dy2 / 2);\r\n        while (y1 != y2)\r\n        {\r\n            if (error >= 0 && (error || (iy > 0)))\r\n            {\r\n                error -= dy2;\r\n                x1 += ix;\r\n            }\r\n\r\n            error += dx2;\r\n            y1 += iy;\r\n            img[x1, y1] = color;\r\n        }\r\n    }\r\n}\r\n\r\n/// Draw a filled circle\r\nvoid drawCircle(SuperImage img, Color4f col, int x0, int y0, uint r)\r\n{\r\n    int f = 1 - r;\r\n    int ddF_x = 0;\r\n    int ddF_y = -2 * r;\r\n    int x = 0;\r\n    int y = r;\r\n\r\n    img[x0, y0 + r] = col;\r\n    img[x0, y0 - r] = col;\r\n    img[x0 + r, y0] = col;\r\n    img[x0 - r, y0] = col;\r\n\r\n    while(x < y)\r\n    {\r\n        if(f >= 0)\r\n        {\r\n            y--;\r\n            ddF_y += 2;\r\n            f += ddF_y;\r\n        }\r\n        x++;\r\n        ddF_x += 2;\r\n        f += ddF_x + 1;\r\n        img[x0 + x, y0 + y] = col;\r\n        img[x0 - x, y0 + y] = col;\r\n        img[x0 + x, y0 - y] = col;\r\n        img[x0 - x, y0 - y] = col;\r\n        img[x0 + y, y0 + x] = col;\r\n        img[x0 - y, y0 + x] = col;\r\n        img[x0 + y, y0 - x] = col;\r\n        img[x0 - y, y0 - x] = col;\r\n    }\r\n}\r\n\r\n/// Draw a filled rectangle\r\nvoid drawRect(SuperImage img, Color4f col, int x1, int y1, int x2, int y2)\r\n{\r\n    int minX = x1 < x2 ? x1 : x2;\r\n    int maxX = x1 > x2 ? x1 : x2;\r\n    int minY = y1 < y2 ? y1 : y2;\r\n    int maxY = y1 > y2 ? y1 : y2;\r\n\r\n    foreach (x; minX..maxX + 1)\r\n        foreach (y; minY..maxY + 1)\r\n            img[x, y] = col;\r\n}\r\n"
  },
  {
    "path": "dlib/image/render/text.d",
    "content": "/*\nCopyright (c) 2022-2025 Oleg Baharev, Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Draw ASCII text\n *\n * Copyright: Oleg Baharev, Timur Gafarov 2022-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Oleg Baharev, Timur Gafarov\n */\nmodule dlib.image.render.text;\n\nimport dlib.image.color;\nimport dlib.image.image;\n\n/// Draw text on an image\nvoid drawText(SuperImage img, string s, int x, int y, Color4f color)\n{\n    foreach(i, c; s)\n    {\n        foreach(cx; 0..5)\n        foreach(cy; 0..8)\n        {\n            Color4f col = sampleChar(c, cx, cy, color);\n            uint px = x + 6 * cast(int)i + cx;\n            uint py = y + cy;\n            img[px, py] = alphaOver(img[px, py], col);\n        }\n    }\n}\n\nColor4f sampleChar(char c, int x, int y, Color4f color)\n{\n    uint index = cast(uint)c * 5;\n    if (x < 0) x = 0;\n    if (x >= 5) x = 4;\n    if (y < 0) y = 0;\n    if (y >= 5 * 8) y = 5 * 8 - 1;\n    ubyte col = font[index + x];\n    auto val = col & (0x01 << y);\n    if (val)\n        return color;\n    else\n        return Color4f(0, 0, 0, 0);\n}\n\n/* \n * 5x8 ASCII character data.\n * Each 5 byte chunk represents one character (5 columns, 8 bits per column)\n */\nimmutable(ubyte)[] font =\n[\n    0x00, 0x00, 0x00, 0x00, 0x00,\n    0x3E, 0x5B, 0x4F, 0x5B, 0x3E,\n    0x3E, 0x6B, 0x4F, 0x6B, 0x3E,\n    0x1C, 0x3E, 0x7C, 0x3E, 0x1C,\n    0x18, 0x3C, 0x7E, 0x3C, 0x18,\n    0x1C, 0x57, 0x7D, 0x57, 0x1C,\n    0x1C, 0x5E, 0x7F, 0x5E, 0x1C,\n    0x00, 0x18, 0x3C, 0x18, 0x00,\n    0xFF, 0xE7, 0xC3, 0xE7, 0xFF,\n    0x00, 0x18, 0x24, 0x18, 0x00,\n    0xFF, 0xE7, 0xDB, 0xE7, 0xFF,\n    0x30, 0x48, 0x3A, 0x06, 0x0E,\n    0x26, 0x29, 0x79, 0x29, 0x26,\n    0x40, 0x7F, 0x05, 0x05, 0x07,\n    0x40, 0x7F, 0x05, 0x25, 0x3F,\n    0x5A, 0x3C, 0xE7, 0x3C, 0x5A,\n    0x7F, 0x3E, 0x1C, 0x1C, 0x08,\n    0x08, 0x1C, 0x1C, 0x3E, 0x7F,\n    0x14, 0x22, 0x7F, 0x22, 0x14,\n    0x5F, 0x5F, 0x00, 0x5F, 0x5F,\n    0x06, 0x09, 0x7F, 0x01, 0x7F,\n    0x00, 0x66, 0x89, 0x95, 0x6A,\n    0x60, 0x60, 0x60, 0x60, 0x60,\n    0x94, 0xA2, 0xFF, 0xA2, 0x94,\n    0x08, 0x04, 0x7E, 0x04, 0x08,\n    0x10, 0x20, 0x7E, 0x20, 0x10,\n    0x08, 0x08, 0x2A, 0x1C, 0x08,\n    0x08, 0x1C, 0x2A, 0x08, 0x08,\n    0x1E, 0x10, 0x10, 0x10, 0x10,\n    0x0C, 0x1E, 0x0C, 0x1E, 0x0C,\n    0x30, 0x38, 0x3E, 0x38, 0x30,\n    0x06, 0x0E, 0x3E, 0x0E, 0x06,\n    0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x5F, 0x00, 0x00,\n    0x00, 0x07, 0x00, 0x07, 0x00,\n    0x14, 0x7F, 0x14, 0x7F, 0x14,\n    0x24, 0x2A, 0x7F, 0x2A, 0x12,\n    0x23, 0x13, 0x08, 0x64, 0x62,\n    0x36, 0x49, 0x56, 0x20, 0x50,\n    0x00, 0x08, 0x07, 0x03, 0x00,\n    0x00, 0x1C, 0x22, 0x41, 0x00,\n    0x00, 0x41, 0x22, 0x1C, 0x00,\n    0x2A, 0x1C, 0x7F, 0x1C, 0x2A,\n    0x08, 0x08, 0x3E, 0x08, 0x08,\n    0x00, 0x80, 0x70, 0x30, 0x00,\n    0x08, 0x08, 0x08, 0x08, 0x08,\n    0x00, 0x00, 0x60, 0x60, 0x00,\n    0x20, 0x10, 0x08, 0x04, 0x02,\n    0x3E, 0x51, 0x49, 0x45, 0x3E,\n    0x00, 0x42, 0x7F, 0x40, 0x00,\n    0x72, 0x49, 0x49, 0x49, 0x46,\n    0x21, 0x41, 0x49, 0x4D, 0x33,\n    0x18, 0x14, 0x12, 0x7F, 0x10,\n    0x27, 0x45, 0x45, 0x45, 0x39,\n    0x3C, 0x4A, 0x49, 0x49, 0x31,\n    0x41, 0x21, 0x11, 0x09, 0x07,\n    0x36, 0x49, 0x49, 0x49, 0x36,\n    0x46, 0x49, 0x49, 0x29, 0x1E,\n    0x00, 0x00, 0x14, 0x00, 0x00,\n    0x00, 0x40, 0x34, 0x00, 0x00,\n    0x00, 0x08, 0x14, 0x22, 0x41,\n    0x14, 0x14, 0x14, 0x14, 0x14,\n    0x00, 0x41, 0x22, 0x14, 0x08,\n    0x02, 0x01, 0x59, 0x09, 0x06,\n    0x3E, 0x41, 0x5D, 0x59, 0x4E,\n    0x7C, 0x12, 0x11, 0x12, 0x7C,\n    0x7F, 0x49, 0x49, 0x49, 0x36,\n    0x3E, 0x41, 0x41, 0x41, 0x22,\n    0x7F, 0x41, 0x41, 0x41, 0x3E,\n    0x7F, 0x49, 0x49, 0x49, 0x41,\n    0x7F, 0x09, 0x09, 0x09, 0x01,\n    0x3E, 0x41, 0x41, 0x51, 0x73,\n    0x7F, 0x08, 0x08, 0x08, 0x7F,\n    0x00, 0x41, 0x7F, 0x41, 0x00,\n    0x20, 0x40, 0x41, 0x3F, 0x01,\n    0x7F, 0x08, 0x14, 0x22, 0x41,\n    0x7F, 0x40, 0x40, 0x40, 0x40,\n    0x7F, 0x02, 0x1C, 0x02, 0x7F,\n    0x7F, 0x04, 0x08, 0x10, 0x7F,\n    0x3E, 0x41, 0x41, 0x41, 0x3E,\n    0x7F, 0x09, 0x09, 0x09, 0x06,\n    0x3E, 0x41, 0x51, 0x21, 0x5E,\n    0x7F, 0x09, 0x19, 0x29, 0x46,\n    0x26, 0x49, 0x49, 0x49, 0x32,\n    0x03, 0x01, 0x7F, 0x01, 0x03,\n    0x3F, 0x40, 0x40, 0x40, 0x3F,\n    0x1F, 0x20, 0x40, 0x20, 0x1F,\n    0x3F, 0x40, 0x38, 0x40, 0x3F,\n    0x63, 0x14, 0x08, 0x14, 0x63,\n    0x03, 0x04, 0x78, 0x04, 0x03,\n    0x61, 0x59, 0x49, 0x4D, 0x43,\n    0x00, 0x7F, 0x41, 0x41, 0x41,\n    0x02, 0x04, 0x08, 0x10, 0x20,\n    0x00, 0x41, 0x41, 0x41, 0x7F,\n    0x04, 0x02, 0x01, 0x02, 0x04,\n    0x40, 0x40, 0x40, 0x40, 0x40,\n    0x00, 0x03, 0x07, 0x08, 0x00,\n    0x20, 0x54, 0x54, 0x78, 0x40,\n    0x7F, 0x28, 0x44, 0x44, 0x38,\n    0x38, 0x44, 0x44, 0x44, 0x28,\n    0x38, 0x44, 0x44, 0x28, 0x7F,\n    0x38, 0x54, 0x54, 0x54, 0x18,\n    0x00, 0x08, 0x7E, 0x09, 0x02,\n    0x18, 0xA4, 0xA4, 0x9C, 0x78,\n    0x7F, 0x08, 0x04, 0x04, 0x78,\n    0x00, 0x44, 0x7D, 0x40, 0x00,\n    0x20, 0x40, 0x40, 0x3D, 0x00,\n    0x7F, 0x10, 0x28, 0x44, 0x00,\n    0x00, 0x41, 0x7F, 0x40, 0x00,\n    0x7C, 0x04, 0x78, 0x04, 0x78,\n    0x7C, 0x08, 0x04, 0x04, 0x78,\n    0x38, 0x44, 0x44, 0x44, 0x38,\n    0xFC, 0x18, 0x24, 0x24, 0x18,\n    0x18, 0x24, 0x24, 0x18, 0xFC,\n    0x7C, 0x08, 0x04, 0x04, 0x08,\n    0x48, 0x54, 0x54, 0x54, 0x24,\n    0x04, 0x04, 0x3F, 0x44, 0x24,\n    0x3C, 0x40, 0x40, 0x20, 0x7C,\n    0x1C, 0x20, 0x40, 0x20, 0x1C,\n    0x3C, 0x40, 0x30, 0x40, 0x3C,\n    0x44, 0x28, 0x10, 0x28, 0x44,\n    0x4C, 0x90, 0x90, 0x90, 0x7C,\n    0x44, 0x64, 0x54, 0x4C, 0x44,\n    0x00, 0x08, 0x36, 0x41, 0x00,\n    0x00, 0x00, 0x77, 0x00, 0x00,\n    0x00, 0x41, 0x36, 0x08, 0x00,\n    0x02, 0x01, 0x02, 0x04, 0x02,\n    0x3C, 0x26, 0x23, 0x26, 0x3C,\n    0x1E, 0xA1, 0xA1, 0x61, 0x12,\n    0x3A, 0x40, 0x40, 0x20, 0x7A,\n    0x38, 0x54, 0x54, 0x55, 0x59,\n    0x21, 0x55, 0x55, 0x79, 0x41,\n    0x22, 0x54, 0x54, 0x78, 0x42,\n    0x21, 0x55, 0x54, 0x78, 0x40,\n    0x20, 0x54, 0x55, 0x79, 0x40,\n    0x0C, 0x1E, 0x52, 0x72, 0x12,\n    0x39, 0x55, 0x55, 0x55, 0x59,\n    0x39, 0x54, 0x54, 0x54, 0x59,\n    0x39, 0x55, 0x54, 0x54, 0x58,\n    0x00, 0x00, 0x45, 0x7C, 0x41,\n    0x00, 0x02, 0x45, 0x7D, 0x42,\n    0x00, 0x01, 0x45, 0x7C, 0x40,\n    0x7D, 0x12, 0x11, 0x12, 0x7D,\n    0xF0, 0x28, 0x25, 0x28, 0xF0,\n    0x7C, 0x54, 0x55, 0x45, 0x00,\n    0x20, 0x54, 0x54, 0x7C, 0x54,\n    0x7C, 0x0A, 0x09, 0x7F, 0x49,\n    0x32, 0x49, 0x49, 0x49, 0x32,\n    0x3A, 0x44, 0x44, 0x44, 0x3A,\n    0x32, 0x4A, 0x48, 0x48, 0x30,\n    0x3A, 0x41, 0x41, 0x21, 0x7A,\n    0x3A, 0x42, 0x40, 0x20, 0x78,\n    0x00, 0x9D, 0xA0, 0xA0, 0x7D,\n    0x3D, 0x42, 0x42, 0x42, 0x3D,\n    0x3D, 0x40, 0x40, 0x40, 0x3D,\n    0x3C, 0x24, 0xFF, 0x24, 0x24,\n    0x48, 0x7E, 0x49, 0x43, 0x66,\n    0x2B, 0x2F, 0xFC, 0x2F, 0x2B,\n    0xFF, 0x09, 0x29, 0xF6, 0x20,\n    0xC0, 0x88, 0x7E, 0x09, 0x03,\n    0x20, 0x54, 0x54, 0x79, 0x41,\n    0x00, 0x00, 0x44, 0x7D, 0x41,\n    0x30, 0x48, 0x48, 0x4A, 0x32,\n    0x38, 0x40, 0x40, 0x22, 0x7A,\n    0x00, 0x7A, 0x0A, 0x0A, 0x72,\n    0x7D, 0x0D, 0x19, 0x31, 0x7D,\n    0x26, 0x29, 0x29, 0x2F, 0x28,\n    0x26, 0x29, 0x29, 0x29, 0x26,\n    0x30, 0x48, 0x4D, 0x40, 0x20,\n    0x38, 0x08, 0x08, 0x08, 0x08,\n    0x08, 0x08, 0x08, 0x08, 0x38,\n    0x2F, 0x10, 0xC8, 0xAC, 0xBA,\n    0x2F, 0x10, 0x28, 0x34, 0xFA,\n    0x00, 0x00, 0x7B, 0x00, 0x00,\n    0x08, 0x14, 0x2A, 0x14, 0x22,\n    0x22, 0x14, 0x2A, 0x14, 0x08,\n    0x55, 0x00, 0x55, 0x00, 0x55,\n    0xAA, 0x55, 0xAA, 0x55, 0xAA,\n    0xFF, 0x55, 0xFF, 0x55, 0xFF,\n    0x00, 0x00, 0x00, 0xFF, 0x00,\n    0x10, 0x10, 0x10, 0xFF, 0x00,\n    0x14, 0x14, 0x14, 0xFF, 0x00,\n    0x10, 0x10, 0xFF, 0x00, 0xFF,\n    0x10, 0x10, 0xF0, 0x10, 0xF0,\n    0x14, 0x14, 0x14, 0xFC, 0x00,\n    0x14, 0x14, 0xF7, 0x00, 0xFF,\n    0x00, 0x00, 0xFF, 0x00, 0xFF,\n    0x14, 0x14, 0xF4, 0x04, 0xFC,\n    0x14, 0x14, 0x17, 0x10, 0x1F,\n    0x10, 0x10, 0x1F, 0x10, 0x1F,\n    0x14, 0x14, 0x14, 0x1F, 0x00,\n    0x10, 0x10, 0x10, 0xF0, 0x00,\n    0x00, 0x00, 0x00, 0x1F, 0x10,\n    0x10, 0x10, 0x10, 0x1F, 0x10,\n    0x10, 0x10, 0x10, 0xF0, 0x10,\n    0x00, 0x00, 0x00, 0xFF, 0x10,\n    0x10, 0x10, 0x10, 0x10, 0x10,\n    0x10, 0x10, 0x10, 0xFF, 0x10,\n    0x00, 0x00, 0x00, 0xFF, 0x14,\n    0x00, 0x00, 0xFF, 0x00, 0xFF,\n    0x00, 0x00, 0x1F, 0x10, 0x17,\n    0x00, 0x00, 0xFC, 0x04, 0xF4,\n    0x14, 0x14, 0x17, 0x10, 0x17,\n    0x14, 0x14, 0xF4, 0x04, 0xF4,\n    0x00, 0x00, 0xFF, 0x00, 0xF7,\n    0x14, 0x14, 0x14, 0x14, 0x14,\n    0x14, 0x14, 0xF7, 0x00, 0xF7,\n    0x14, 0x14, 0x14, 0x17, 0x14,\n    0x10, 0x10, 0x1F, 0x10, 0x1F,\n    0x14, 0x14, 0x14, 0xF4, 0x14,\n    0x10, 0x10, 0xF0, 0x10, 0xF0,\n    0x00, 0x00, 0x1F, 0x10, 0x1F,\n    0x00, 0x00, 0x00, 0x1F, 0x14,\n    0x00, 0x00, 0x00, 0xFC, 0x14,\n    0x00, 0x00, 0xF0, 0x10, 0xF0,\n    0x10, 0x10, 0xFF, 0x10, 0xFF,\n    0x14, 0x14, 0x14, 0xFF, 0x14,\n    0x10, 0x10, 0x10, 0x1F, 0x00,\n    0x00, 0x00, 0x00, 0xF0, 0x10,\n    0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    0xF0, 0xF0, 0xF0, 0xF0, 0xF0,\n    0xFF, 0xFF, 0xFF, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0xFF, 0xFF,\n    0x0F, 0x0F, 0x0F, 0x0F, 0x0F,\n    0x38, 0x44, 0x44, 0x38, 0x44,\n    0xFC, 0x4A, 0x4A, 0x4A, 0x34,\n    0x7E, 0x02, 0x02, 0x06, 0x06,\n    0x02, 0x7E, 0x02, 0x7E, 0x02,\n    0x63, 0x55, 0x49, 0x41, 0x63,\n    0x38, 0x44, 0x44, 0x3C, 0x04,\n    0x40, 0x7E, 0x20, 0x1E, 0x20,\n    0x06, 0x02, 0x7E, 0x02, 0x02,\n    0x99, 0xA5, 0xE7, 0xA5, 0x99,\n    0x1C, 0x2A, 0x49, 0x2A, 0x1C,\n    0x4C, 0x72, 0x01, 0x72, 0x4C,\n    0x30, 0x4A, 0x4D, 0x4D, 0x30,\n    0x30, 0x48, 0x78, 0x48, 0x30,\n    0xBC, 0x62, 0x5A, 0x46, 0x3D,\n    0x3E, 0x49, 0x49, 0x49, 0x00,\n    0x7E, 0x01, 0x01, 0x01, 0x7E,\n    0x2A, 0x2A, 0x2A, 0x2A, 0x2A,\n    0x44, 0x44, 0x5F, 0x44, 0x44,\n    0x40, 0x51, 0x4A, 0x44, 0x40,\n    0x40, 0x44, 0x4A, 0x51, 0x40,\n    0x00, 0x00, 0xFF, 0x01, 0x03,\n    0xE0, 0x80, 0xFF, 0x00, 0x00,\n    0x08, 0x08, 0x6B, 0x6B, 0x08,\n    0x36, 0x12, 0x36, 0x24, 0x36,\n    0x06, 0x0F, 0x09, 0x0F, 0x06,\n    0x00, 0x00, 0x18, 0x18, 0x00,\n    0x00, 0x00, 0x10, 0x10, 0x00,\n    0x30, 0x40, 0xFF, 0x01, 0x01,\n    0x00, 0x1F, 0x01, 0x01, 0x1E,\n    0x00, 0x19, 0x1D, 0x17, 0x12,\n    0x00, 0x3C, 0x3C, 0x3C, 0x3C\n];\n"
  },
  {
    "path": "dlib/image/resampling/bicubic.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Bicubic resampling\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.resampling.bicubic;\r\n\r\nimport std.math;\r\nimport dlib.image.image;\r\nimport dlib.image.color;\r\n\r\nT bicubic(T) (T x)\r\n{\r\n    if (x > 2.0)\r\n        return 0.0;\r\n\r\n    T a, b, c, d;\r\n    T xm1 = x - 1.0;\r\n    T xp1 = x + 1.0;\r\n    T xp2 = x + 2.0;\r\n\r\n    a = ( xp2 <= 0.0 ) ? 0.0 : xp2 * xp2 * xp2;\r\n    b = ( xp1 <= 0.0 ) ? 0.0 : xp1 * xp1 * xp1;\r\n    c = ( x   <= 0.0 ) ? 0.0 : x * x * x;\r\n    d = ( xm1 <= 0.0 ) ? 0.0 : xm1 * xm1 * xm1;\r\n\r\n    return ( 0.16666666666666666667 *\r\n           ( a - ( 4.0 * b ) + ( 6.0 * c ) - ( 4.0 * d ) ) );\r\n}\r\n\r\n/// Resize image with bicubic filter\r\nSuperImage resampleBicubic(SuperImage img, uint newWidth, uint newHeight)\r\nin\r\n{\r\n    assert (img.data.length);\r\n}\r\ndo\r\n{\r\n    SuperImage res = img.createSameFormat(newWidth, newHeight);\r\n\r\n    float xFactor = cast(float)img.width  / cast(float)newWidth;\r\n    float yFactor = cast(float)img.height / cast(float)newHeight;\r\n\r\n    foreach(x; 0..res.width)\r\n    {\r\n        float ox = x * xFactor - 0.5f;\r\n        int ox1 = cast(int)ox;\r\n        float dx = ox - ox1;\r\n\r\n        foreach(y; 0..res.height)\r\n        {\r\n            float oy = y * yFactor - 0.5f;\r\n            int oy1 = cast(int)oy;\r\n            float dy = oy - oy1;\r\n\r\n            Color4f colSum = Color4f(0, 0, 0);\r\n\r\n            foreach(kx; -1..3)\r\n            {\r\n                int ix = ox1 + kx;\r\n\r\n                if (ix < 0) ix = 0;\r\n                if (ix >= img.width) ix = img.width - 1;\r\n\r\n                foreach(ky; -1..3)\r\n                {\r\n                    int iy = oy1 + ky;\r\n\r\n                    if (iy < 0) iy = 0;\r\n                    if (iy >= img.height) iy = img.height - 1;\r\n\r\n                    auto col = img[ix, iy];\r\n\r\n                    float k1 = bicubic(dy - cast(float)ky);\r\n                    float k2 = k1 * bicubic(cast(float)kx - dx);\r\n\r\n                    colSum += col * k2;\r\n                }\r\n            }\r\n\r\n            res[x, y] = colSum;\r\n        }\r\n    }\r\n\r\n    return res;\r\n}\r\n"
  },
  {
    "path": "dlib/image/resampling/bilinear.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Bilinear resampling\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.resampling.bilinear;\r\n\r\nimport std.math;\r\nimport dlib.image.image;\r\nimport dlib.image.color;\r\n\r\n/// Resize image with bilinear filter\r\nSuperImage resampleBilinear(SuperImage img, in uint newWidth, in uint newHeight)\r\nin\r\n{\r\n    assert (img.data.length);\r\n}\r\ndo\r\n{\r\n    SuperImage res = img.createSameFormat(newWidth, newHeight);\r\n\r\n    float xFactor = cast(float)img.width  / cast(float)newWidth;\r\n    float yFactor = cast(float)img.height / cast(float)newHeight;\r\n\r\n    int floor_x, floor_y, ceil_x, ceil_y;\r\n    float fraction_x, fraction_y, one_minus_x, one_minus_y;\r\n\r\n    Color4f c1, c2, c3, c4;\r\n    Color4f col;\r\n    float b1, b2;\r\n\r\n    foreach(y; 0..res.height)\r\n    foreach(x; 0..res.width)\r\n    {\r\n        floor_x = cast(int)floor(x * xFactor);\r\n        floor_y = cast(int)floor(y * yFactor);\r\n\r\n        ceil_x = floor_x + 1;\r\n        if (ceil_x >= img.width)\r\n            ceil_x = floor_x;\r\n\r\n        ceil_y = floor_y + 1;\r\n        if (ceil_y >= img.height)\r\n            ceil_y = floor_y;\r\n\r\n        fraction_x = x * xFactor - floor_x;\r\n        fraction_y = y * yFactor - floor_y;\r\n        one_minus_x = 1.0f - fraction_x;\r\n        one_minus_y = 1.0f - fraction_y;\r\n\r\n        c1 = img[floor_x, floor_y];\r\n        c2 = img[ceil_x,  floor_y];\r\n        c3 = img[floor_x, ceil_y];\r\n        c4 = img[ceil_x,  ceil_y];\r\n\r\n        // Red\r\n        b1 = one_minus_x * c1.r + fraction_x * c2.r;\r\n        b2 = one_minus_x * c3.r + fraction_x * c4.r;\r\n        col.r = one_minus_y * b1 + fraction_y * b2;\r\n\r\n        // Green\r\n        b1 = one_minus_x * c1.g + fraction_x * c2.g;\r\n        b2 = one_minus_x * c3.g + fraction_x * c4.g;\r\n        col.g = one_minus_y * b1 + fraction_y * b2;\r\n\r\n        // Blue\r\n        b1 = one_minus_x * c1.b + fraction_x * c2.b;\r\n        b2 = one_minus_x * c3.b + fraction_x * c4.b;\r\n        col.b = one_minus_y * b1 + fraction_y * b2;\r\n\r\n        // Alpha\r\n        b1 = one_minus_x * c1.a + fraction_x * c2.a;\r\n        b2 = one_minus_x * c3.a + fraction_x * c4.a;\r\n        col.a = one_minus_y * b1 + fraction_y * b2;\r\n\r\n        res[x, y] = col;\r\n    }\r\n\r\n    return res;\r\n}\r\n"
  },
  {
    "path": "dlib/image/resampling/lanczos.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Lanczos resampling\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.resampling.lanczos;\r\n\r\nimport std.math;\r\nimport dlib.image.image;\r\nimport dlib.image.color;\r\n\r\nT lanczos(T) (T x, int filterSize)\r\n{\r\n    if (x <= -filterSize || x >= filterSize)\r\n        return 0.0; // Outside of the window\r\n    if (x > -T.epsilon && x < T.epsilon)\r\n        return 1.0; // Special case the discontinuity at the origin\r\n\r\n    auto sinc = (T x) => sin(PI * x) / (PI * x);\r\n    return sinc(x) * sinc(x / filterSize);\r\n}\r\n\r\n/// Resize image with Lanczos filter\r\nSuperImage resampleLanczos(SuperImage img, in uint newWidth, in uint newHeight)\r\nin\r\n{\r\n    assert (img.data.length);\r\n}\r\ndo\r\n{\r\n    SuperImage res = img.createSameFormat(newWidth, newHeight);\r\n\r\n    float xFactor = cast(float)img.width  / cast(float)newWidth;\r\n    float yFactor = cast(float)img.height / cast(float)newHeight;\r\n\r\n    foreach(x; 0..res.width)\r\n    {\r\n        float ox = x * xFactor - 0.5f;\r\n        int ox1 = cast(int)ox;\r\n        float dx = ox - ox1;\r\n\r\n        foreach(y; 0..res.height)\r\n        {\r\n            float oy = y * yFactor - 0.5f;\r\n            int oy1 = cast(int)oy;\r\n            float dy = oy - oy1;\r\n\r\n            Color4f colSum = Color4f(0, 0, 0);\r\n            float kSum;\r\n\r\n            foreach(kx; -3..4)\r\n            {\r\n                int ix = ox1 + kx;\r\n\r\n                if (ix < 0) ix = 0;\r\n                if (ix >= img.width) ix = img.width - 1;\r\n\r\n                foreach(ky; -3..4)\r\n                {\r\n                    int iy = oy1 + ky;\r\n\r\n                    if (iy < 0) iy = 0;\r\n                    if (iy >= img.height) iy = img.height - 1;\r\n\r\n                    auto col = img[ix, iy];\r\n\r\n                    float k1 = lanczos((cast(float)ky - dy), 3);\r\n                    float k2 = k1 * lanczos((cast(float)kx - dx), 3);\r\n\r\n                    kSum += k2;\r\n\r\n                    colSum += col * k2;\r\n                }\r\n            }\r\n\r\n            if (kSum > 0.0f)\r\n                colSum /= kSum;\r\n\r\n            res[x, y] = colSum;\r\n        }\r\n    }\r\n\r\n    return res;\r\n}\r\n"
  },
  {
    "path": "dlib/image/resampling/nearest.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Nearest-neighbor resampling\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.resampling.nearest;\r\n\r\nimport dlib.image.image;\r\nimport dlib.image.color;\r\n\r\n/// Resize image with nearest-neighbor filter\r\nSuperImage resampleNearestNeighbor(SuperImage img, in uint newWidth, in uint newHeight)\r\nin\r\n{\r\n    assert (img.data.length);\r\n}\r\ndo\r\n{\r\n    SuperImage res = img.createSameFormat(newWidth, newHeight);\r\n\r\n    float scaleWidth  = cast(float)newWidth / cast(float)img.width;\r\n    float scaleHeight = cast(float)newHeight / cast(float)img.height;\r\n\r\n    uint nearest_x, nearest_y;\r\n\r\n    foreach(y; 0..res.height)\r\n    foreach(x; 0..res.width)\r\n    {\r\n        nearest_x = cast(uint)(x / scaleWidth);\r\n        nearest_y = cast(uint)(y / scaleHeight);\r\n        res[x, y] = img[nearest_x, nearest_y];\r\n    }\r\n\r\n    return res;\r\n}\r\n"
  },
  {
    "path": "dlib/image/resampling/package.d",
    "content": "/*\nCopyright (c) 2022-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Image resizing algorithms\n *\n * Copyright: Timur Gafarov 2022-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.image.resampling;\n\npublic\n{\n    import dlib.image.resampling.nearest;\n    import dlib.image.resampling.bilinear;\n    import dlib.image.resampling.bicubic;\n    import dlib.image.resampling.lanczos;\n}\n"
  },
  {
    "path": "dlib/image/signal2d.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * 2D signal consisting of complex number data\r\n *\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.signal2d;\r\n\r\nprivate\r\n{\r\n    import dlib.core.memory;\r\n    import dlib.math.vector;\r\n    import dlib.math.complex;\r\n    import dlib.math.fft;\r\n    import dlib.image.image;\r\n    import dlib.image.color;\r\n}\r\n\r\n/// Signal domain\r\nenum SignalDomain\r\n{\r\n    Spatial,\r\n    Frequency\r\n}\r\n\r\n/**\r\n * 2D signal consisting of complex number data\r\n */\r\nclass Signal2D\r\n{\r\n    Complexf[] data;\r\n    uint width;\r\n    uint height;\r\n    SignalDomain domain = SignalDomain.Spatial;\r\n\r\n    this(uint w, uint h)\r\n    {\r\n        width = w;\r\n        height = h;\r\n        data = New!(Complexf[])(width * height);\r\n    }\r\n\r\n    this(SuperImage img, uint channel)\r\n    {\r\n        width = img.width;\r\n        height = img.height;\r\n        data = New!(Complexf[])(width * height);\r\n\r\n        foreach(y; 0..height)\r\n        foreach(x; 0..width)\r\n        {\r\n            auto col = img[x, y];\r\n            size_t index = (y * width + x);\r\n            data[index].re = col.vec[channel];\r\n            data[index].im = 0.0f;\r\n        }\r\n    }\r\n    \r\n    ~this()\r\n    {\r\n        Delete(data);\r\n    }\r\n\r\n    void fft()\r\n    {\r\n        foreach(y; 0..height)\r\n        foreach(x; 0..width)\r\n        {\r\n            if (((x + y) & 0x1) != 0)\r\n            {\r\n                size_t index = (y * width + x);\r\n                data[index].re = data[index].re * -1;\r\n                data[index].im = data[index].im * -1;\r\n            }\r\n        }\r\n\r\n        fft2(true);\r\n        domain = SignalDomain.Frequency;\r\n    }\r\n\r\n    void fftInverse()\r\n    {\r\n        if (domain != SignalDomain.Frequency)\r\n            return;\r\n\r\n        fft2(false);\r\n        domain = SignalDomain.Spatial;\r\n\r\n        foreach(y; 0..height)\r\n        foreach(x; 0..width)\r\n        {\r\n            if (((x + y) & 0x1) != 0)\r\n            {\r\n                size_t index = (y * width + x);\r\n                data[index].re = data[index].re * -1;\r\n                data[index].im = data[index].im * -1;\r\n            }\r\n        }\r\n    }\r\n\r\n    void multiply(Signal2D img, Signal2D dest)\r\n    {\r\n        assert(img.width == width && img.height == height && dest.width == width && dest.height == height);\r\n\r\n        dest.domain = domain;\r\n\r\n        foreach(i, v; data)\r\n        {\r\n            dest.data[i] = v * img.data[i];\r\n        }\r\n    }\r\n    \r\n    void divide(Signal2D img, Signal2D dest)\r\n    {\r\n        assert(img.width == width && img.height == height && dest.width == width && dest.height == height);\r\n\r\n        dest.domain = domain;\r\n\r\n        foreach(i, v; data)\r\n        {\r\n            if (img.data[i].re == 0.0f)\r\n                dest.data[i] = v;\r\n            else\r\n                dest.data[i] = v / img.data[i];\r\n        }\r\n    }\r\n\r\n    Complexf opIndex(int x, int y)\r\n    {\r\n        while(x >= width) x = width-1;\r\n        while(y >= height) y = height-1;\r\n        while(x < 0) x = 0;\r\n        while(y < 0) y = 0;\r\n\r\n        return data[y * width + x];\r\n    }\r\n\r\n    void fft2(bool forward)\r\n    {\r\n        // process rows\r\n        Complexf[] row = New!(Complexf[])(width);\r\n        for (int y = 0; y < height; y++)\r\n        {\r\n            for (int x = 0; x < width; x++)\r\n            {\r\n                auto index = y * width + x;\r\n                row[x] = data[index];\r\n            }\r\n\r\n            fastFourierTransform(row, forward);\r\n\r\n            for (int x = 0; x < width; x++)\r\n            {\r\n                auto index = y * width + x;\r\n                data[index] = row[x];\r\n            }\r\n        }\r\n\r\n        // process columns\r\n        Complexf[] col = New!(Complexf[])(height);\r\n        for (int x = 0; x < width; x++)\r\n        {\r\n            for (int y = 0; y < height; y++)\r\n            {\r\n                auto index = y * width + x;\r\n                col[y] = data[index];\r\n            }\r\n\r\n            fastFourierTransform(col, forward);\r\n\r\n            for (int y = 0; y < height; y++)\r\n            {\r\n                auto index = y * width + x;\r\n                data[index] = col[y];\r\n            }\r\n        }\r\n        \r\n        Delete(row);\r\n        Delete(col);\r\n    }\r\n}\r\n\r\n/// Convert Signal2D to SuperImage\r\nvoid signalToImage(Signal2D chRed, Signal2D chGreen, Signal2D chBlue, SuperImage img)\r\n{\r\n    float maxIntensityR = 0.0f;\r\n    float maxIntensityG = 0.0f;\r\n    float maxIntensityB = 0.0f;\r\n    Vector3f[] fpImage = New!(Vector3f[])(img.width * img.height);\r\n\r\n    for(int i = 0; i < chRed.data.length; i++)\r\n    {\r\n        float mr, mg, mb;\r\n\r\n        mr = chRed.data[i].magnitude;\r\n        if (mr > maxIntensityR)\r\n            maxIntensityR = mr;\r\n        mg = chGreen.data[i].magnitude;\r\n        if (mg > maxIntensityG)\r\n            maxIntensityG = mg;\r\n        mb = chBlue.data[i].magnitude;\r\n        if (mb > maxIntensityB)\r\n            maxIntensityB = mb;\r\n\r\n        fpImage[i] = Vector3f(mr, mg, mb);\r\n    }\r\n\r\n    foreach(ref v; fpImage)\r\n    {\r\n        v.r /= maxIntensityR;\r\n        v.g /= maxIntensityG;\r\n        v.b /= maxIntensityB;\r\n    }\r\n\r\n    foreach(y; 0..img.height)\r\n    foreach(x; 0..img.width)\r\n    {\r\n        size_t index = (y * img.width + x);\r\n        Vector3f m = fpImage[index];\r\n        img[x, y] = Color4f(m);\r\n    }\r\n\r\n    Delete(fpImage);\r\n}\r\n"
  },
  {
    "path": "dlib/image/transform.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Geometric ransformations of images\r\n *\r\n * Copyright: Timur Gafarov 2016-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.transform;\r\n\r\nimport dlib.math.vector;\r\nimport dlib.math.matrix;\r\nimport dlib.math.transformation;\r\nimport dlib.math.utils;\r\n\r\nimport dlib.image.image;\r\nimport dlib.image.color;\r\n\r\n/// Tranforms an image with affine 3x3 matrix\r\nSuperImage affineTransformImage(SuperImage img, SuperImage outp, Matrix3x3f m)\r\n{\r\n    SuperImage res;\r\n    if (outp)\r\n        res = outp;\r\n    else\r\n        res = img.createSameFormat(img.width, img.height);\r\n\r\n    foreach(y; 0..res.height)\r\n    foreach(x; 0..res.width)\r\n    {\r\n        Vector2f v1 = Vector2f(x, y).affineTransform2D(m);\r\n        res[x, y] = bilinearPixel(img, v1.x, v1.y);\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/// ditto\r\nSuperImage affineTransformImage(SuperImage img, Matrix3x3f m)\r\n{\r\n    return affineTransformImage(img, null, m);\r\n}\r\n\r\n/// Translates an image (positive x goes right, positive y goes down)\r\nSuperImage translateImage(SuperImage img, SuperImage outp, Vector2f t)\r\n{\r\n    Matrix3x3f m = translationMatrix2D(-t);\r\n    return affineTransformImage(img, outp, m);\r\n}\r\n\r\n/// ditto\r\nSuperImage translateImage(SuperImage img, Vector2f t)\r\n{\r\n    return translateImage(img, null, t);\r\n}\r\n\r\n/// Rotates an image clockwise around its center. Angle is in degrees.\r\nSuperImage rotateImage(SuperImage img, SuperImage outp, float angle)\r\n{\r\n    Vector2f center = Vector2f(img.width, img.height) * 0.5f;\r\n    Matrix3x3f m =\r\n      translationMatrix2D(center) *\r\n      rotationMatrix2D(degtorad(angle)) *\r\n      translationMatrix2D(-center);\r\n    return affineTransformImage(img, outp, m);\r\n}\r\n\r\n/// ditto\r\nSuperImage rotateImage(SuperImage img, float angle)\r\n{\r\n    return rotateImage(img, null, angle);\r\n}\r\n\r\n/// Scales an image\r\nSuperImage scaleImage(SuperImage img, SuperImage outp, Vector2f s)\r\n{\r\n    Matrix3x3f m = scaleMatrix2D(Vector2f(1, 1) / s);\r\n    return affineTransformImage(img, outp, m);\r\n}\r\n\r\n/// ditto\r\nSuperImage scaleImage(SuperImage img, Vector2f s)\r\n{\r\n    return scaleImage(img, null, s);\r\n}\r\n\r\n/// Uniformly scales an image\r\nSuperImage scaleImage(SuperImage img, SuperImage outp, float s)\r\n{\r\n    float sinv = 1.0f / s;\r\n    Matrix3x3f m = scaleMatrix2D(Vector2f(sinv, sinv));\r\n    return affineTransformImage(img, outp, m);\r\n}\r\n\r\n/// ditto\r\nSuperImage scaleImage(SuperImage img, float s)\r\n{\r\n    return scaleImage(img, null, s);\r\n}\r\n"
  },
  {
    "path": "dlib/image/unmanaged.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * GC-free SuperImage implementation\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.image.unmanaged;\r\n\r\nimport dlib.image.image;\r\nimport dlib.core.memory;\r\n\r\n/**\r\n * Image that uses dlib.core.memory instead of GC\r\n */\r\nclass UnmanagedImage(IntegerPixelFormat fmt): Image!(fmt)\r\n{\r\n    override @property SuperImage dup()\r\n    {\r\n        auto res = New!(UnmanagedImage!(fmt))(_width, _height);\r\n        res.data[] = data[];\r\n        return res;\r\n    }\r\n\r\n    override SuperImage createSameFormat(uint w, uint h)\r\n    {\r\n        return New!(UnmanagedImage!(fmt))(w, h);\r\n    }\r\n\r\n    this(uint w, uint h)\r\n    {\r\n        super(w, h);\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        Delete(_data);\r\n    }\r\n\r\n    protected override void allocateData()\r\n    {\r\n        _data = New!(ubyte[])(_width * _height * _pixelSize);\r\n    }\r\n\r\n    override void free()\r\n    {\r\n        Delete(this);\r\n    }\r\n}\r\n\r\n/// Specialization of UnmanagedImage for 8-bit luminance pixel format\r\nalias UnmanagedImageL8 = UnmanagedImage!(IntegerPixelFormat.L8);\r\n/// Specialization of UnmanagedImage for 8-bit luminance-alpha pixel format\r\nalias UnmanagedImageLA8 = UnmanagedImage!(IntegerPixelFormat.LA8);\r\n/// Specialization of UnmanagedImage for 8-bit RGB pixel format\r\nalias UnmanagedImageRGB8 = UnmanagedImage!(IntegerPixelFormat.RGB8);\r\n/// Specialization of UnmanagedImage for 8-bit RGBA pixel format\r\nalias UnmanagedImageRGBA8 = UnmanagedImage!(IntegerPixelFormat.RGBA8);\r\n\r\n/// Specialization of UnmanagedImage for 16-bit luminance pixel format\r\nalias UnmanagedImageL16 = UnmanagedImage!(IntegerPixelFormat.L16);\r\n/// Specialization of UnmanagedImage for 16-bit luminance-alpha pixel format\r\nalias UnmanagedImageLA16 = UnmanagedImage!(IntegerPixelFormat.LA16);\r\n/// Specialization of UnmanagedImage for 16-bit RGB pixel format\r\nalias UnmanagedImageRGB16 = UnmanagedImage!(IntegerPixelFormat.RGB16);\r\n/// Specialization of UnmanagedImage for 16-bit RGBA pixel format\r\nalias UnmanagedImageRGBA16 = UnmanagedImage!(IntegerPixelFormat.RGBA16);\r\n\r\n/**\r\n * UnmanagedImage factory class\r\n */\r\nclass UnmanagedImageFactory: SuperImageFactory\r\n{\r\n    SuperImage createImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1)\r\n    {\r\n        return unmanagedImage(w, h, channels, bitDepth);\r\n    }\r\n}\r\n\r\n/**\r\n * UnmanagedImage factory function\r\n */\r\nSuperImage unmanagedImage(uint w, uint h, uint channels = 3, uint bitDepth = 8)\r\nin\r\n{\r\n    assert(channels > 0 && channels <= 4);\r\n    assert(bitDepth == 8 || bitDepth == 16);\r\n}\r\ndo\r\n{\r\n    switch(channels)\r\n    {\r\n        case 1:\r\n        {\r\n            if (bitDepth == 8)\r\n                return New!UnmanagedImageL8(w, h);\r\n            else\r\n                return New!UnmanagedImageL16(w, h);\r\n        }\r\n        case 2:\r\n        {\r\n            if (bitDepth == 8)\r\n                return New!UnmanagedImageLA8(w, h);\r\n            else\r\n                return New!UnmanagedImageLA16(w, h);\r\n        }\r\n        case 3:\r\n        {\r\n            if (bitDepth == 8)\r\n                return New!UnmanagedImageRGB8(w, h);\r\n            else\r\n                return New!UnmanagedImageRGB16(w, h);\r\n        }\r\n        case 4:\r\n        {\r\n            if (bitDepth == 8)\r\n                return New!UnmanagedImageRGBA8(w, h);\r\n            else\r\n                return New!UnmanagedImageRGBA16(w, h);\r\n        }\r\n        default:\r\n            assert(0);\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/math/combinatorics.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Nick Papanastasiou, Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Combinatorics\r\n *\r\n * Copyright: Nick Papanastasiou 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Nick Papanastasiou, Timur Gafarov\r\n */\r\nmodule dlib.math.combinatorics;\r\n\r\nimport std.functional: memoize;\r\nimport std.algorithm: reduce, map;\r\nimport std.range: iota;\r\nimport std.bigint;\r\n\r\n/// Returns the factorial of n\r\nulong factorial(ulong n) @safe nothrow\r\n{\r\n    if(n <= 1)\r\n    {\r\n        return 1;\r\n    }\r\n\r\n    alias mfac = memoize!factorial;\r\n\r\n    return n * mfac(n - 1);\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(factorial(10) == 3_628_800);\r\n\r\n    int n = 5;\r\n    assert(n.factorial == 5.factorial && 5.factorial == 120);\r\n}\r\n\r\n/// Computes the nth fibonacci number\r\nulong fibonacci(ulong n)\r\n{\r\n    if(n == 0 || n == 1)\r\n    {\r\n        return n;\r\n    }\r\n\r\n    alias mfib = memoize!fibonacci;\r\n\r\n    return mfib(n - 1) + mfib(n - 2);\r\n}\r\n\r\n/// Common vernacular for fibonacci\r\nalias fib = fibonacci;\r\n\r\n///\r\nunittest\r\n{\r\n    import std.array: array;\r\n    auto fibs = iota(1, 21).map!(n => fib(n)).array;\r\n    assert(fibs == [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,\r\n                    233, 377, 610, 987, 1597, 2584, 4181, 6765]);\r\n}\r\n\r\n\r\n/// Computes the double factorial of n: n * (n - 2) * (n - 4) * ... * 1\r\nulong doubleFactorial(ulong n)\r\n{\r\n    if (n <= 1)\r\n    {\r\n        return 1;\r\n    }\r\n\r\n    alias mDoubleFac = memoize!doubleFactorial;\r\n\r\n    return n * doubleFactorial(n - 2);\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    import std.array: array;\r\n    auto dfacs = iota(1, 21).map!(n => doubleFactorial(n)).array;\r\n    assert(dfacs == [1, 2, 3, 8, 15, 48, 105, 384, 945, 3840, \r\n                    10395, 46080, 135135, 645120, 2027025, \r\n                    10321920, 34459425, 185794560, 654729075, \r\n                    3715891200]);\r\n}\r\n\r\n/// Computes the hyperfactorial of n: 1^1 * 2^2 * 3^3 * ... n^n\r\nBigInt hyperFactorial(ulong n)\r\n{\r\n    if(n <= 1)\r\n    {\r\n        return BigInt(\"1\");\r\n    }\r\n\r\n    alias mhfac = memoize!hyperFactorial;\r\n\r\n    return BigInt(n ^^ n) * hyperFactorial(n - 1);\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    import std.array: array;\r\n    auto hfacs = iota(1, 6).map!(n => hyperFactorial(n)).array;\r\n    assert(hfacs == [1, 4, 108, 27648, 86400000]);\r\n}\r\n\r\n/++\r\n+ Compute the number of combinations of `objects` types of items\r\n+ when considered `taken` at a time, where order is ignored\r\n+/\r\nulong combinations(ulong objects, ulong taken) @safe nothrow\r\n{\r\n    if (objects < taken)\r\n    {\r\n        return 0;\r\n    }\r\n\r\n    return objects.factorial / (taken.factorial * (objects - taken).factorial);\r\n}\r\n\r\n/// Common vernacular for combinations\r\nalias C = combinations;\r\n\r\n/// Ditto\r\nalias choose = combinations;\r\n\r\n///\r\nunittest\r\n{\r\n    assert(1.choose(2) == 0);\r\n    assert(5.choose(2) == 10);\r\n}\r\n\r\n/++\r\n+  Compute the number of permutations of `objects` types of items\r\n+ when considered `taken` at a time, where order is considered\r\n+/\r\nulong permutations(ulong objects, ulong taken) @safe nothrow\r\n{\r\n    return objects.factorial / (objects - taken).factorial;\r\n}\r\n\r\n// Common vernacular for permutations\r\nalias P = permutations;\r\n\r\n///\r\nunittest\r\n{\r\n    assert(10.P(2) == 90);\r\n}\r\n\r\n/// Computes the nth Lucas number\r\nulong lucas(ulong n) @safe nothrow\r\n{\r\n    if (n == 0)\r\n    {\r\n        return 2;\r\n    }\r\n\r\n    if (n == 1)\r\n    {\r\n        return 1;\r\n    }\r\n\r\n    alias mlucas = memoize!lucas;\r\n\r\n    return mlucas(n - 1) + mlucas(n - 2);\r\n}\r\n\r\nunittest\r\n{\r\n    import std.algorithm: map;\r\n    import std.array;\r\n    auto lucasRange = iota(0, 12).map!(k => lucas(k)).array;\r\n    assert(lucasRange == [2, 1, 3, 4, 7, 11, 18, 29, 47, 76, 123, 199]);\r\n}\r\n"
  },
  {
    "path": "dlib/math/complex.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Complex numbers\r\n *\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.math.complex;\r\n\r\nimport std.math;\r\nimport std.range;\r\nimport std.format;\r\n\r\n/// Complex number representation\r\nstruct Complex(T)\r\n{\r\n    T re;\r\n    T im;\r\n\r\n    this(T r, T i)\r\n    {\r\n        re = r;\r\n        im = i;\r\n    }\r\n\r\n    this(T r)\r\n    {\r\n        re = r;\r\n        im = 0.0;\r\n    }\r\n\r\n    Complex!(T) opUnary(string op) () if (op == \"-\")\r\n    {\r\n        return Complex!(T)(re, -im);\r\n    }\r\n\r\n    Complex!(T) opBinary(string op)(Complex!(T) c) if (op == \"+\")\r\n    {\r\n        return Complex!(T)(re + c.re, im + c.im);\r\n    }\r\n\r\n    Complex!(T) opBinary(string op)(Complex!(T) c) if (op == \"-\")\r\n    {\r\n        return Complex!(T)(re - c.re, im - c.im);\r\n    }\r\n\r\n    Complex!(T) opBinary(string op)(Complex!(T) c) if (op == \"*\")\r\n    {\r\n        return Complex!(T)(\r\n            re * c.re - im * c.im,\r\n            re * c.im + im * c.re);\r\n    }\r\n\r\n    Complex!(T) opBinary(string op)(Complex!(T) c) if (op == \"/\")\r\n    {\r\n        T denominator = c.re * c.re + c.im * c.im;\r\n        return Complex!(T)(\r\n            (re * c.re + im * c.im) / denominator,\r\n            (im * c.re - re * c.im) / denominator);\r\n    }\r\n\r\n    Complex!(T) opOpAssign(string op)(Complex!(T) c) if (op == \"+\")\r\n    {\r\n        re += c.re;\r\n        im += c.im;\r\n        return this;\r\n    }\r\n\r\n    Complex!(T) opOpAssign(string op)(Complex!(T) c) if (op == \"-\")\r\n    {\r\n        re -= c.re;\r\n        im -= c.im;\r\n        return this;\r\n    }\r\n\r\n    Complex!(T) opOpAssign(string op)(Complex!(T) c) if (op == \"*\")\r\n    {\r\n        T temp = re;\r\n        re = re * c.re - im * c.im;\r\n        im = im * c.re + temp * c.im;\r\n        return this;\r\n    }\r\n\r\n    Complex!(T) opOpAssign(string op)(Complex!(T) c) if (op == \"/\")\r\n    {\r\n        T denominator = c.re * c.re + c.im * c.im;\r\n        T temp = re;\r\n        re = (re * c.re + im * c.im) / denominator;\r\n        im = (im * c.re - temp * c.im) / denominator;\r\n        return this;\r\n    }\r\n\r\n    Complex!(T) opBinary(string op)(T scalar) if (op == \"+\")\r\n    {\r\n        return Complex!(T)(re + scalar, im + scalar);\r\n    }\r\n\r\n    Complex!(T) opBinary(string op)(T scalar) if (op == \"-\")\r\n    {\r\n        return Complex!(T)(re + scalar, im + scalar);\r\n    }\r\n\r\n    Complex!(T) opBinary(string op)(T scalar) if (op == \"*\")\r\n    {\r\n        return Complex!(T)(re * scalar, im * scalar);\r\n    }\r\n\r\n    Complex!(T) opBinary(string op)(T scalar) if (op == \"/\")\r\n    {\r\n        return Complex!(T)(re / scalar, im / scalar);\r\n    }\r\n\r\n    Complex!(T) reciprocal()\r\n    {\r\n        T scale = re * re + im * im;\r\n        return Complex!(T)(re / scale, -im / scale);\r\n    }\r\n\r\n    T magnitude()\r\n    {\r\n        return sqrt(re * re + im * im);\r\n    }\r\n\r\n    T norm()\r\n    {\r\n        return (re * re + im * im);\r\n    }\r\n\r\n    string toString()\r\n    {\r\n        auto writer = appender!string();\r\n        formattedWrite(writer, \"%s + %si\", re, im);\r\n        return writer.data;\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    import dlib.math.utils;\r\n    \r\n    auto c1 = Complex!float(2, 1);\r\n    auto c2 = Complex!float(1, 2);\r\n    auto c3 = Complex!float(1);\r\n    \r\n    assert((c1 + c2) == Complex!float(3, 3));\r\n    assert((c1 - c1) == Complex!float(0, 0));\r\n    assert((c1 * c2) == Complex!float(0, 5));\r\n    assert((c2 / c1) == Complex!float(0.8f, 0.6f));\r\n    assert(c1.reciprocal == Complex!float(0.4f, -0.2f));\r\n    assert(c3.magnitude == 1.0f);\r\n    assert(c3.norm == 1.0f);\r\n    assert(c1.toString == \"2 + 1i\");\r\n}\r\n\r\n/// Complex abs\r\nT abs(T)(Complex!T x)\r\n{\r\n    return sqrt(x.re * x.re + x.im * x.im);\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    auto c1 = Complex!float(1);\r\n    assert(abs(c1) == 1.0f);\r\n}\r\n\r\n/// Complex atan2\r\nT atan2(T)(Complex!T x)\r\n{\r\n    return std.math.atan2(x.im, x.re);\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    auto c1 = Complex!float(1);\r\n    assert(atan2(c1) == 0.0f);\r\n}\r\n\r\n/// Complex pow\r\nComplex!T pow(T)(Complex!T x, Complex!T n)\r\n{\r\n    T r = abs(x);\r\n    T t = x.magnitude;\r\n    T c = n.re;\r\n    T d = n.im;\r\n\r\n    Complex!T res;\r\n    res.re = std.math.pow(r, c) * std.math.exp(-d*t) * cos(c*t + d*log(r));\r\n    res.im = std.math.pow(r, c) * std.math.exp(-d*t) * sin(c*t + d*log(r));\r\n\r\n    return res;\r\n}\r\n\r\n/// Complex exp\r\nComplex!T exp(T)(Complex!T s)\r\n{\r\n    return Complex!T(\r\n        std.math.exp(s.re) * cos(s.im),\r\n        std.math.exp(s.re) * sin(s.im));\r\n}\r\n\r\n/*\r\n * Predefined complex type aliases\r\n */\r\nalias Complexf = Complex!(float);\r\nalias Complexd = Complex!(double);\r\n"
  },
  {
    "path": "dlib/math/decomposition.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Matrix decomposition\r\n *\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.math.decomposition;\r\n\r\nimport std.math;\r\nimport dlib.math.matrix;\r\n\r\n/**\r\n * LUP decomposition.\r\n * Factors a matrix A into PA = LU,\r\n * where L is a lower triangular matrix,\r\n * U is an upper triangular matrix,\r\n * and P is a permutation matrix.\r\n */\r\nvoid decomposeLUP(T, size_t N)(\r\n      Matrix!(T,N) A,\r\n  ref Matrix!(T,N) L,\r\n  ref Matrix!(T,N) U,\r\n  ref Matrix!(T,N) P)\r\n{\r\n    Matrix!(T,N) C = A;\r\n    P = Matrix!(T,N).identity;\r\n\r\n    for (size_t i = 0; i < N; i++)\r\n    {\r\n        T pivotValue = 0.0;\r\n        size_t pivot;\r\n        for (size_t row = i; row < N; row++)\r\n        {\r\n            if (abs(C[row, i]) > pivotValue)\r\n            {\r\n                pivotValue = abs(C[row, i]);\r\n                pivot = row;\r\n            }\r\n        }\r\n\r\n        assert(pivotValue != 0.0);\r\n\r\n        P.swapRows(pivot, i);\r\n        C.swapRows(pivot, i);\r\n\r\n        for (size_t j = i + 1; j < N; j++)\r\n        {\r\n            C[j, i] = C[j, i] / C[i, i];\r\n            for (size_t k = i + 1; k < N; k++)\r\n                C[j, k] = C[j, k] - C[j, i] * C[i, k];\r\n        }\r\n    }\r\n\r\n    L = Matrix!(T,N).identity;\r\n    U = Matrix!(T,N).zero;\r\n    for (size_t i = 0; i < N; i++)\r\n    {\r\n        for (size_t j = 0; j < N-1-i; j++)\r\n            L[N-1-i, j] = C[N-1-i, j];\r\n        for (size_t j = i; j < N; j++)\r\n            U[i, j] = C[i, j];\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/math/diff.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Automatic differentiation\r\n *\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.math.diff;\r\n\r\nimport dlib.math.dual;\r\nimport dlib.core.compound;\r\n\r\n/**\r\n * Differentiate a function of a single argument\r\n * Examples:\r\n * ---\r\n * auto r = diff!f(x);\r\n * r[0] // = f(x)\r\n * r[1] // = f'(x)\r\n * ---\r\n */\r\nauto diff(alias F, T)(T x)\r\n{\r\n    auto eval = F(Dual!(T)(x, 1.0));\r\n    return compound(eval.re, eval.du);\r\n}\r\n"
  },
  {
    "path": "dlib/math/dual.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Dual numbers\r\n *\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.math.dual;\r\n\r\nprivate\r\n{\r\n    import std.math;\r\n}\r\n\r\n/**\r\n * Dual number representation\r\n */\r\nstruct Dual(T)\r\n{\r\n    T re;\r\n    T du;\r\n\r\n    this(T r)\r\n    {\r\n        re = r;\r\n        du = 0.0;\r\n    }\r\n\r\n    this(T r, T d)\r\n    {\r\n        re = r;\r\n        du = d;\r\n    }\r\n\r\n    this(in int e)\r\n    {\r\n        re = cast(T)e;\r\n        du = 0.0;\r\n    }\r\n\r\n    static Dual!(T) opCast(in T x)\r\n    {\r\n        return Dual!(T)(x, 0.0);\r\n    }\r\n\r\n    Dual!(T) opAssign(in T x)\r\n    {\r\n        re = x;\r\n        du = 0.0;\r\n        return this;\r\n    }\r\n\r\n    Dual!(T) opBinary(string op)(in T x) const if (op == \"+\")\r\n    {\r\n        return this + Dual!(T)(x);\r\n    }\r\n\r\n    Dual!(T) opBinary(string op)(in T x) const if (op == \"-\")\r\n    {\r\n        return this - Dual!(T)(x);\r\n    }\r\n\r\n    Dual!(T) opBinary(string op)(in T x) const if (op == \"*\")\r\n    {\r\n        return this * Dual!(T)(x);\r\n    }\r\n\r\n    Dual!(T) opBinary(string op)(in T x) const if (op == \"/\")\r\n    {\r\n        return this / Dual!(T)(x);\r\n    }\r\n\r\n    Dual!(T) opUnary(string s)() const if (s == \"-\")\r\n    {\r\n        return Dual!(T)(-re, -du);\r\n    }\r\n\r\n    Dual!(T) opBinary(string op)(in Dual!(T) x) const if (op == \"+\")\r\n    {\r\n        return Dual!(T)(\r\n            re + x.re,\r\n            du + x.du\r\n        );\r\n    }\r\n\r\n    Dual!(T) opBinary(string op)(in Dual!(T) x) const if (op == \"-\")\r\n    {\r\n        return Dual!(T)(\r\n            re - x.re,\r\n            du - x.du\r\n        );\r\n    }\r\n\r\n    Dual!(T) opBinary(string op)(in Dual!(T) x) const if (op == \"*\")\r\n    {\r\n        return Dual!(T)(\r\n            re * x.re,\r\n            re * x.du + du * x.re\r\n        );\r\n    }\r\n\r\n    Dual!(T) opBinary(string op)(in Dual!(T) x) const if (op == \"/\")\r\n    {\r\n        return Dual!(T)(\r\n            re / x.re,\r\n            (re * x.du - du * x.re) / (x.re * x.re)\r\n        );\r\n    }\r\n\r\n    Dual!(T) opOpAssign(string op)(in Dual!(T) x) if (op == \"+\")\r\n    {\r\n        re += x.re;\r\n        du += x.du;\r\n        return this;\r\n    }\r\n\r\n    Dual!(T) opOpAssign(string op)(in Dual!(T) x) if (op == \"-\")\r\n    {\r\n        re -= x.re;\r\n        du -= x.du;\r\n        return this;\r\n    }\r\n\r\n    Dual!(T) opOpAssign(string op)(in Dual!(T) x) if (op == \"*\")\r\n    {\r\n        re *= x.re,\r\n        du = re * x.du + du * x.re;\r\n        return this;\r\n    }\r\n\r\n    Dual!(T) opOpAssign(string op)(in Dual!(T) x) if (op == \"/\")\r\n    {\r\n        re /= x.re,\r\n        du = (re * x.du - du * x.re) / (x.re * x.re);\r\n        return this;\r\n    }\r\n\r\n    Dual!(T) sqrt() const\r\n    {\r\n        T tmp = std.math.sqrt(re);\r\n        return Dual!(T)(\r\n            tmp,\r\n            du / (2.0 * tmp)\r\n        );\r\n    }\r\n\r\n    Dual!(T) opBinaryRight(string op)(in T v) const if (op == \"+\")\r\n    {\r\n        return Dual!(T)(v) + this;\r\n    }\r\n\r\n    Dual!(T) opBinaryRight(string op)(in T v) const if (op == \"-\")\r\n    {\r\n        return Dual!(T)(v) - this;\r\n    }\r\n\r\n    Dual!(T) opBinaryRight(string op)(in T v) const if (op == \"*\")\r\n    {\r\n        return Dual!(T)(v) * this;\r\n    }\r\n\r\n    Dual!(T) opBinaryRight(string op)(in T v) const if (op == \"/\")\r\n    {\r\n        return Dual!(T)(v) / this;\r\n    }\r\n\r\n    int opCmp(in double s) const\r\n    {\r\n        return cast(int)((re - s) * 100);\r\n    }\r\n\r\n    Dual!(T) sin() const\r\n    {\r\n        return Dual!(T)(.sin(re), du * .cos(re));\r\n    }\r\n\r\n    Dual!(T) cos() const\r\n    {\r\n        return Dual!(T)(.cos(re), -du * .sin(re));\r\n    }\r\n\r\n    Dual!(T) exp() const\r\n    {\r\n        return Dual!(T)(.exp(re), du * .exp(re));\r\n    }\r\n\r\n    Dual!(T) pow(T k) const\r\n    {\r\n        return Dual!(T)(re^^k, k * (re^^(k-1)) * du);\r\n    }\r\n}\r\n\r\n/// Alias for single precision Dual specialization\r\nalias Dualf = Dual!(float);\r\n\r\n/// Alias for double precision Dual specialization\r\nalias Duald = Dual!(double);\r\n\r\n///\r\nunittest\r\n{\r\n    Dualf a = Dualf(1.0f, 1.0f);\r\n    assert(a + a == Dualf(2.0f, 2.0f));\r\n    assert(a - a == Dualf(0.0f, 0.0f));\r\n    assert(a * a == Dualf(1.0f, 2.0f));\r\n    assert(a / a == Dualf(1.0f, 0.0f));\r\n    \r\n    assert(Dualf(4.0f, 1.0f).sqrt == Dualf(2.0f, 0.25f));\r\n}\r\n"
  },
  {
    "path": "dlib/math/dualquaternion.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Dual quaternions\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.math.dualquaternion;\r\n\r\nimport std.math;\r\nimport std.range;\r\nimport std.format;\r\n\r\nimport dlib.math.vector;\r\nimport dlib.math.matrix;\r\nimport dlib.math.quaternion;\r\nimport dlib.math.transformation;\r\nimport dlib.math.dual;\r\n\r\n/**\r\n * Dual quaternion representation.\r\n * Dual quaternion is a generalization of quaternion to dual numbers field.\r\n * Similar to the way that simple quaternion represents rotation in 3D space,\r\n * dual quaternion represents rigid 3D transformation (translation + rotation),\r\n * so it can be used in kinematics.\r\n */\r\nstruct DualQuaternion(T)\r\n{\r\n    this(Quaternion!(T) q1, Quaternion!(T) q2)\r\n    {\r\n        this.q1 = q1;\r\n        this.q2 = q2;\r\n    }\r\n\r\n    this(Quaternion!(T) r, Vector!(T,3) t)\r\n    {\r\n        this.q1 = r;\r\n        this.q2 = Quaternion!(T)(t * 0.5, 0.0) * r;\r\n    }\r\n\r\n    this(Quaternion!(T) r)\r\n    {\r\n        this.q1 = r;\r\n        this.q2 = Quaternion!(T).identity * r;\r\n    }\r\n\r\n    this(Vector!(T,3) t)\r\n    {\r\n        this.q1 = Quaternion!(T).identity;\r\n        this.q2 = Quaternion!(T)(t * 0.5, 0.0);\r\n    }\r\n\r\n    Vector!(T,3) transform(Vector!(T,3) v)\r\n    {\r\n        auto vq = DualQuaternion!(T)(\r\n            Quaternion!(T).identity,\r\n            Quaternion!(T)(v.x, v.y, v.z, 0.0));\r\n        auto q = this * vq * this.fullConjugate;\r\n        return q.q2.xyz;\r\n    }\r\n\r\n    Vector!(T,3) rotate(Vector!(T,3) v)\r\n    {\r\n        return q1.rotate(v);\r\n    }\r\n\r\n    DualQuaternion!(T) conjugate()\r\n    {\r\n        return DualQuaternion!(T)(q1.conj, q2.conj);\r\n    }\r\n\r\n    DualQuaternion!(T) dualConjugate()\r\n    {\r\n        return DualQuaternion!(T)(q1, q2 * -1.0);\r\n    }\r\n\r\n    DualQuaternion!(T) fullConjugate()\r\n    {\r\n        return DualQuaternion!(T)(q1.conj, q2.conj * -1.0);\r\n    }\r\n\r\n    DualQuaternion!(T) opBinary(string op)(DualQuaternion!(T) d) if (op == \"*\")\r\n    {\r\n        return DualQuaternion!(T)(q1 * d.q1, q1 * d.q2 + q2 * d.q1);\r\n    }\r\n\r\n    DualQuaternion!(T) opBinary(string op)(DualQuaternion!(T) d) if (op == \"+\")\r\n    {\r\n        return DualQuaternion!(T)(q1 + d.q1, q2 + d.q2);\r\n    }\r\n\r\n    DualQuaternion!(T) opBinary(string op)(DualQuaternion!(T) d) if (op == \"-\")\r\n    {\r\n        return DualQuaternion!(T)(q1 - d.q1, q2 - d.q2);\r\n    }\r\n\r\n   /**\r\n    * Rotation part\r\n    */\r\n    Quaternion!(T) rotation()\r\n    {\r\n        return q1;\r\n    }\r\n\r\n   /**\r\n    * Translation part\r\n    */\r\n    Vector!(T,3) translation()\r\n    {\r\n        return (2.0 * q2 * q1.conj).xyz;\r\n    }\r\n\r\n   /**\r\n    * Convert to 4x4 matrix\r\n    */\r\n    Matrix!(T,4) toMatrix4x4()\r\n    {\r\n        // TODO: Can this be done without matrix multiplication?\r\n        return translationMatrix(translation) * rotation.toMatrix4x4;\r\n    }\r\n\r\n   /**\r\n    * Dual quaternion norm\r\n    */\r\n    Dual!(T) norm()\r\n    {\r\n        auto qq = this * this.conjugate;\r\n        return Dual!(T)(qq.q1.lengthsqr, qq.q2.lengthsqr).sqrt;\r\n    }\r\n\r\n   /**\r\n    * Set norm to 1\r\n    */\r\n    DualQuaternion!(T) normalized()\r\n    {\r\n        Dual!(T) n = norm;\r\n        return DualQuaternion!(T)(q1 / n.re, q2 / n.re);\r\n    }\r\n\r\n   /**\r\n    * Convert to string\r\n    */\r\n    string toString()\r\n    {\r\n        auto writer = appender!string();\r\n        formattedWrite(writer, \"[%s, %s]\", q1.arrayof, q2.arrayof);\r\n        return writer.data;\r\n    }\r\n\r\n   /**\r\n    * Elements union\r\n    */\r\n    union\r\n    {\r\n        struct\r\n        {\r\n            /// Rotation part\r\n            Quaternion!(T) q1;\r\n            \r\n            /// Translation part\r\n            Quaternion!(T) q2;\r\n        }\r\n\r\n        /// Elements as static array\r\n        T[8] arrayof;\r\n    }\r\n}\r\n\r\n/// Alias for single precision DualQuaternion specialization\r\nalias DualQuaternionf = DualQuaternion!(float);\r\n\r\n/// Alias for double precision DualQuaternion specialization\r\nalias DualQuaterniond = DualQuaternion!(double);\r\n\r\n///\r\nunittest\r\n{\r\n    Quaternionf r1 = rotationQuaternion!float(0, PI * 0.5f);\r\n    Vector3f t1 = Vector3f(1.0f, 0.0f, 0.0f);\r\n    DualQuaternionf dq1 = DualQuaternionf(r1, t1);\r\n    assert(dq1.rotation == r1);\r\n    assert(isAlmostZero(dq1.translation - t1));\r\n    \r\n    Vector3f v = dq1.rotate(Vector3f(0.0f, 1.0f, 0.0f));\r\n    assert(isAlmostZero(v - Vector3f(0.0f, 0.0f, 1.0f)));\r\n}\r\n"
  },
  {
    "path": "dlib/math/fft.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Fast Fourier transform\r\n *\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.math.fft;\r\n\r\nimport std.math;\r\nimport dlib.math.utils;\r\nimport dlib.math.complex;\r\n\r\n/// Forward or backward fast Fourier transform. Data must be power of two in length\r\nvoid fastFourierTransform(Complexf[] data, bool forward)\r\n{\r\n    assert(isPowerOfTwo(data.length));\r\n\r\n    uint target = 0;\r\n\r\n    for (uint pos = 0; pos < data.length; ++pos)\r\n    {\r\n        if (target > pos)\r\n        {\r\n            Complexf temp = data[target];\r\n            data[target] = data[pos];\r\n            data[pos] = temp;\r\n        }\r\n        uint mask = cast(uint)data.length;\r\n        while (target & (mask >>= 1))\r\n            target &= ~mask;\r\n        target |= mask;\r\n    }\r\n\r\n    float pi = forward? -PI : PI;\r\n\r\n    for (uint step = 1; step < data.length; step <<= 1)\r\n    {\r\n        uint jump = step << 1;\r\n        float delta = pi / cast(float)step;\r\n        float sine = sin(delta * 0.5f);\r\n        Complexf multiplier = Complexf(-2.0f * sine * sine, sin(delta));\r\n        Complexf factor = Complexf(1.0f);\r\n\r\n        for (uint group = 0; group < step; ++group)\r\n        {\r\n            for (uint pair = group; pair < data.length; pair += jump)\r\n            {\r\n                uint match = pair + step;\r\n                Complexf product = factor * data[match];\r\n                data[match] = data[pair] - product;\r\n                data[pair] += product;\r\n            }\r\n\r\n            factor = multiplier * factor + factor;\r\n        }\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Complexf[4] data =\r\n    [\r\n        Complexf(1.0f, 0.0f),\r\n        Complexf(2.0f, 0.0f),\r\n        Complexf(3.0f, 0.0f),\n        Complexf(4.0f, 0.0f)\r\n    ];\r\n    \r\n    fastFourierTransform(data, true);\r\n    \r\n    assert(data[0].toString == \"10 + 0i\");\r\n    assert(data[1].toString == \"-2 + 2i\");\r\n    assert(data[2].toString == \"-2 + 0i\");\n    assert(data[3].toString == \"-2 + -2i\");\r\n}\r\n"
  },
  {
    "path": "dlib/math/hof.d",
    "content": "/*\nCopyright (c) 2011-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Functions that return other functions\n *\n * Copyright: Timur Gafarov 2011-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.math.hof;\n\n/**\n * Functional composition.\n * Description:\n * Returns a function that applies function f to the return value of function g\n */\nT delegate(S) compose(T, U, S)(T function(U) f, U function(S) g)\n{\n    return (S s) => f(g(s));\n}\n\n/**\n Y combinator\n Description:\n We're all familiar with the idea of a function as something that takes some\n input value and returns some output value. Say, the function for squaring numbers:\n ---\n   f(x) = x*x;\n ---\n The fixed points of a function are any input values for which f(x) is equal to x.\n So, the fixed points of f(x) = x*x are 0 and 1.\n\n Now, we have things called higher-order functions. These are functions that take another\n function as input, or return a function as output, or both.\n\n The fixed point of a higher-order function f is another function p such that f(p) = p.\n It may be more helpful to think in terms of functions actually being executed.\n The previous statement is equivalent to the statement that f(p)(x) = p(x) for all values of x.\n\n Y (the Y combinator) is a special function that returns the fixed points of higher-order\n functions, that is to say:\n ---\n   f(Y(f)) = Y(f)\n ---\n Y combinator is commonly use to allow anonymous recursion without assuming your host\n language supports it.\n*/\nauto Y(R, P...) (R delegate(P) delegate(R delegate(P)) lambda)\n{\n    struct RFunc {R delegate(P) delegate(RFunc) f;}\n    auto r = RFunc((RFunc w) => lambda((P x) => w.f(w)(x)));\n    return r.f(r);\n}\n\n///\nunittest\n{\n    import std.algorithm;\n    import std.range;\n\n    auto factorial = Y((int delegate(int) self) =>\n        (int n) => 0 == n ? 1 : n * self(n - 1));\n\n    auto ackermann = Y((ulong delegate(ulong, ulong) self) =>\n        (ulong m, ulong n) => (!m && n)?\n            (n+1) : (m && !n)? self(m-1, 1) : self(m-1, self(m, n-1)));\n\n    auto qsort = Y((int[] delegate(int[]) self) =>\n        (int[] arr) => arr.length?\n            self(arr.filter!(a => a < arr[0]).array)\n          ~ arr[0]\n          ~ self(arr.filter!(a => a > arr[0]).array) : []);\n\n    assert(factorial(6) == 720);\n    assert(ackermann(3, 5) == 253);\n    assert(qsort([8, 5, 10, 2, 16, 9, 1, 100, 3])\n              == [1, 2, 3, 5, 8, 9, 10, 16, 100]);\n}\n"
  },
  {
    "path": "dlib/math/interpolation/bezier.d",
    "content": "/*\nCopyright (c) 2013-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Bézier interpolation functions\n *\n * Copyright: Timur Gafarov 2013-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.math.interpolation.bezier;\n\nimport dlib.math.vector;\n\n/**\n * Computes quadratic Bézier curve\n */\nT bezierQuadratic(T)(T A, T B, T C, T t)\n{\n    T s = cast(T)1.0 - t;\n    return s * s * A + 2.0 * s * B + t * t * C;\n}\n\n/**\n * Computes cubic Bézier curve\n */\nT bezierCubic(T) (T A, T B, T C, T D, T t)\n{\n    T s = cast(T)1.0 - t;\n    T s2 = s * s;\n    T s3 = s2 * s;\n    return s3 * A +\n           3.0 * t * s2 * B +\n           3.0 * t * t * s * C +\n           t * t * t * D;\n}\n\n/// ditto\nalias bezier = bezierCubic;\n\n/**\n * Computes cubic Bézier curve tangent\n */\nT bezierCubicTangent(T)(T a, T b, T c, T d, T t)\n{\n    T c1 = (d - (3.0 * c) + (3.0 * b) - a);\n    T c2 = ((3.0 * c) - (6.0 * b) + (3.0 * a));\n    T c3 = ((3.0 * b) - (3.0 * a));\n    return ((3.0 * c1 * t * t) + (2.0 * c2 * t) + c3);\n}\n\n/// ditto\nalias bezierTangent = bezierCubicTangent;\n\n/**\n * Computes cubic Bézier curve in 2D space\n */\nVector!(T,2) bezierVector2(T)(\n    Vector!(T,2) a,\n    Vector!(T,2) b,\n    Vector!(T,2) c,\n    Vector!(T,2) d,\n    T t)\n{\n    return Vector!(T,2)\n    (\n        bezier(a.x, b.x, c.x, d.x, t),\n        bezier(a.y, b.y, c.y, d.y, t)\n    );\n}\n\n/**\n * Computes cubic Bézier curve in 3D space\n */\nVector!(T,3) bezierVector3(T)(\n    Vector!(T,3) a,\n    Vector!(T,3) b,\n    Vector!(T,3) c,\n    Vector!(T,3) d,\n    T t)\n{\n    return Vector!(T,3)\n    (\n        bezier(a.x, b.x, c.x, d.x, t),\n        bezier(a.y, b.y, c.y, d.y, t),\n        bezier(a.z, b.z, c.z, d.z, t)\n    );\n}\n\n/**\n * Computes cubic Bézier curve tangent in 2D space\n */\nVector!(T,2) bezierTangentVector2(T)(\n    Vector!(T,2) a,\n    Vector!(T,2) b,\n    Vector!(T,2) c,\n    Vector!(T,2) d,\n    T t)\n{\n    return Vector!(T,2)\n    (\n        bezierTangent(a.x, b.x, c.x, d.x, t),\n        bezierTangent(a.y, b.y, c.y, d.y, t)\n    );\n}\n\n/**\n * Computes cubic Bézier curve tangent in 3D space\n */\nVector!(T,3) bezierTangentVector3(T)(\n    Vector!(T,3) a,\n    Vector!(T,3) b,\n    Vector!(T,3) c,\n    Vector!(T,3) d,\n    T t)\n{\n    return Vector!(T,3)\n    (\n        bezierTangent(a.x, b.x, c.x, d.x, t),\n        bezierTangent(a.y, b.y, c.y, d.y, t),\n        bezierTangent(a.z, b.z, c.z, d.z, t)\n    );\n}\n"
  },
  {
    "path": "dlib/math/interpolation/catmullrom.d",
    "content": "/*\nCopyright (c) 2011-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Catmull-Rom interpolation functions\n *\n * Copyright: Timur Gafarov 2011-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.math.interpolation.catmullrom;\n\nimport std.math;\n\n/**\n * Catmull-Rom curve\n */\nT interpCatmullRom(T) (T p0, T p1, T p2, T p3, float t)\n{\n    return 0.5 * ((2 * p1) +\n                  (-p0 + p2) * t +\n                  (2 * p0 - 5 * p1 + 4 * p2 - p3) * t^^2 +\n                  (-p0 + 3 * p1 - 3 * p2 + p3) * t^^3);\n}\n\n/**\n * Catmull-Rom curve derivative\n */\nT interpCatmullRomDerivative(T) (T p0, T p1, T p2, T p3, float t)\n{\n    return 0.5 * ((2 * p1) +\n                  (-p0 + p2) +\n                  2 * (2 * p0 - 5 * p1 + 4 * p2 - p3) * t +\n                  3 * (-p0 + 3 * p1 - 3 * p2 + p3) * t^^2);\n}\n"
  },
  {
    "path": "dlib/math/interpolation/easing.d",
    "content": "/*\nCopyright (c) 2019-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Easing functions for animation.\n *\n * Copyright: Timur Gafarov 2019-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.math.interpolation.easing;\n\nimport std.math;\n\n/// Quadratic ease in\nT easeInQuad(T)(T t)\n{\n    return t * t;\n}\n\n/// Quadratic ease out\nT easeOutQuad(T)(T t)\n{\n    return -t * (t - 2);\n}\n\n/// Quadratic ease in-out\nT easeInOutQuad(T)(T t)\n{\n    t /= 0.5;\n    if (t < 1.0)\n        return 0.5 * t * t;\n    t--;\n    return -0.5 * (t * (t - 2.0) - 1.0);\n}\n\n/// Back ease in\nT easeInBack(T)(T t)\n{\n    enum T s = 1.70158;\n    return t * t * ((s + 1) * t - s);\n}\n\n/// Back ease out\nT easeOutBack(T)(T t)\n{\n    enum T s = 1.70158;\n    t = t - 1.0;\n    return (t * t * ((s + 1) * t + s) + 1.0);\n}\n\n/// Back ease in-out\nT easeInOutBack(T)(T t)\n{\n    float s = 1.70158;\n    t /= 0.5;\n    if (t < 1.0)\n    {\n        s *= 1.525;\n        return 0.5 * (t * t * ((s + 1.0) * t - s));\n    }\n    t -= 2.0;\n    s *= 1.525;\n    return 0.5 * (t * t * ((s + 1.0) * t + s) + 2.0);\n}\n\n/// Bounce ease out\nT easeOutBounce(T)(T t)\n{\n    if (t < (1.0 / 2.75))\n    {\n        return (7.5625 * t * t);\n    }\n    else if (t < (2.0 / 2.75))\n    {\n        t -= (1.5 / 2.75);\n        return (7.5625 * (t) * t + 0.75);\n    }\n    else if (t < (2.5 / 2.75))\n    {\n        t -= (2.25 / 2.75);\n        return (7.5625 * (t) * t + 0.9375);\n    }\n    else\n    {\n        t -= (2.625 / 2.75);\n        return (7.5625 * (t) * t + 0.984375);\n    }\n}\n\n/// Elastic ease out\nT easeOutElastic(T)(T t)\n{\n    T c4 = (2 * PI) / 3;\n    return t == 0? 0 : t == 1 ? 1 : pow(2, -10 * t) * sin((t * 10 - 0.75) * c4) + 1;\n}\n"
  },
  {
    "path": "dlib/math/interpolation/hermite.d",
    "content": "/*\nCopyright (c) 2013-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Hermite interpolation functions\n *\n * Copyright: Timur Gafarov 2013-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.math.interpolation.hermite;\n\n/**\n * Hermite curve\n */\nT interpHermite(T) (T x, T tx, T y, T ty, float t)\n{\n    float t2 = t * t;\n    float t3 = t2 * t;\n    float h1 = t3 * 2.0 - t2 * 3.0 + 1.0;\n    float h2 = t2 * 3.0 - t3 * 2.0;\n    float h3 = t3 - t2 * 2.0 + t;\n    float h4 = t3 - t2;\n    return x * h1 + tx * h3 + y * h2 + ty * h4;\n}\n\n/**\n * Hermite curve derivative\n */\nT interpHermiteDerivative(T) (T x, T tx, T y, T ty, float t)\n{\n    float t2 = t * t;\n    float h1 = t2 * 6.0 - t * 6.0;\n    float h2 = t * 6.0 - t2 * 6.0;\n    float h3 = t2 * 3.0 - t * 4.0 + 1.0;\n    float h4 = t2 * 3.0 - t * 2.0;\n    return x * h1 + tx * h3 + y * h2 + ty * h4;\n}\n"
  },
  {
    "path": "dlib/math/interpolation/linear.d",
    "content": "/*\nCopyright (c) 2011-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Linear interpolation\n *\n * Copyright: Timur Gafarov 2013-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.math.interpolation.linear;\n\nimport std.math;\n\n/// Linear interpolation\nT interpLinear(T) (T a, T b, float t)\n{\n    return a + (b - a) * t;\n}\n\n/// ditto\nalias lerp = interpLinear;\n\n///\nunittest\n{\n    assert(lerp(0.0f, 2.0f, 0.5f) == 1.0f);\n}\n"
  },
  {
    "path": "dlib/math/interpolation/nearest.d",
    "content": "/*\nCopyright (c) 2011-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Nearest-neighbour interpolation\n *\n * Copyright: Timur Gafarov 2011-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.math.interpolation.nearest;\n\nimport std.math;\n\n/// Nearest-neighbour interpolation\nT interpNearest(T) (T x, T y, float t)\n{\n    if (t < 0.5f)\n        return x;\n    else\n        return y;\n}\n\n///\nunittest\n{\n    assert(interpNearest(0.0f, 2.0f, 0.3f) == 0.0f);\n}\n"
  },
  {
    "path": "dlib/math/interpolation/package.d",
    "content": "/*\nCopyright (c) 2020-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Interpolation functions\n *\n * Copyright: Timur Gafarov 2020-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.math.interpolation;\n\npublic\n{\n    import dlib.math.interpolation.bezier;\n    import dlib.math.interpolation.catmullrom;\n    import dlib.math.interpolation.easing;\n    import dlib.math.interpolation.hermite;\n    import dlib.math.interpolation.linear;\n    import dlib.math.interpolation.nearest;\n    import dlib.math.interpolation.smoothstep;\n}\n"
  },
  {
    "path": "dlib/math/interpolation/smoothstep.d",
    "content": "/*\nCopyright (c) 2018-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Sigmoid-like functions for clamping and non-linear interpolation of values in [0, 1] range.\n *\n * Copyright: Timur Gafarov 2018-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.math.interpolation.smoothstep;\n\nimport std.math;\nimport dlib.math.utils;\n\n/**\n * Hermite polynomial, analogous to GLSL smoothstep.\n * e0 and e1 define lower and upper edges of Hermite function.\n */\nT hermiteSmoothstep(T)(T x, float e0, float e1)\n{\n    T t = clamp((x - e0) / (e1 - e0), 0.0, 1.0);\n    return t * t * (3.0 - 2.0 * t);\n}\n\n/**\n * Rational sigmoid that becomes linear at k=0 and discrete at k=1.\n * Allows varying between linear and nearest-neighbour interpolation.\n */\nT rationalSmoothstep(T)(T x, float k)\n{\n    T s = (x + x * k - k * 0.5 - 0.5) / (abs(x * k * 4.0 - k * 2.0) - k + 1.0) + 0.5;\n    return clamp(s, 0.0, 1.0);\n}\n"
  },
  {
    "path": "dlib/math/linsolve.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Linear equation system solvers\r\n *\r\n * Description:\r\n * A system is given in matrix form: \r\n * ---\r\n * Ax = b\r\n * ---\r\n * For example:\r\n * ---\r\n * x + 3y - 2z = 5\r\n * 3x + 5y + 6z = 7\r\n * 2x + 4y + 3z = 8 \r\n * ---\r\n * For this system, A (coefficient matrix) will be\r\n * ---\r\n * [1, 3, -2]\r\n * [3, 5,  6]\r\n * [2, 4,  3]\r\n * ---\r\n * And b (right side vector) will be \r\n * ---\r\n * [5, 7, 8]\r\n * ---\r\n * x is a vector of unknowns:\r\n * ---\r\n * [x, y, z]\r\n * ---\r\n *\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.math.linsolve;\r\n\r\nimport std.math;\r\nimport dlib.math.matrix;\r\nimport dlib.math.vector;\nimport dlib.math.decomposition;\r\n\r\n// TODO: use arrays instead of Matrix structs to support big systems stored in heap\r\n\r\n/// Solve Ax = b using LUP decomposition\nvoid solve(T, size_t N)(\n      Matrix!(T,N) a,\n  ref Vector!(T,N) x,\n      Vector!(T,N) b)\n{\n    Matrix!(T,N) L, U, P;\n    decomposeLUP(a, L, U, P);\n    solveLU(L, U, x, b * P);\n}\r\n\r\n///\r\nunittest\r\n{\r\n    bool isConsiderZeroTolerant(T) (T f) nothrow\r\n    {\r\n        return (abs(f) < 0.0001f);\r\n    }\r\n    \r\n    Matrix3f a = matrixf(\r\n        1, 3, -2,\r\n        3, 5,  6,\r\n        2, 4,  3\r\n    );\r\n    Vector3f b = Vector3f(5, 7, 8);\r\n    Vector3f x = Vector3f(0, 0, 0);\r\n    \r\n    solve(a, x, b);\r\n    \r\n    assert(isConsiderZeroTolerant(-15 - x[0]));\r\n    assert(isConsiderZeroTolerant(8 - x[1]));\r\n    assert(isConsiderZeroTolerant(2 - x[2]));\r\n}\r\n\r\n/// Solve LUx = b\r\nvoid solveLU(T, size_t N)(\r\n    Matrix!(T,N) L,\r\n    Matrix!(T,N) U,\r\nref Vector!(T,N) x,\r\n    Vector!(T,N) b)\r\n{\r\n    int i = 0;\r\n    int j = 0;\r\n    Vector!(T,N) y;\r\n\r\n    // Forward solve Ly = b\r\n    for (i = 0; i < N; i++)\r\n    {\r\n        y[i] = b[i];\r\n        for (j = 0; j < i; j++)\r\n            y[i] -= L[i, j] * y[j];\r\n        y[i] /= L[i, i];\r\n    }\r\n\r\n    // Backward solve Ux = y\r\n    for (i = N - 1; i >= 0; i--)\r\n    {\r\n        x[i] = y[i];\r\n        for (j = i + 1; j < N; j++)\r\n            x[i] -= U[i, j] * x[j];\r\n        x[i] /= U[i, i];\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/math/matrix.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov, Martin Cejp\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Square matrices with static memory allocation\r\n *\r\n * Copyright: Timur Gafarov, Martin Cejp 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov, Martin Cejp\r\n */\r\nmodule dlib.math.matrix;\r\n\r\nimport std.math;\r\nimport std.range;\r\nimport std.format;\r\nimport std.conv;\r\nimport std.string;\r\n\r\nimport dlib.math.vector;\r\nimport dlib.math.utils;\r\nimport dlib.math.decomposition;\r\nimport dlib.math.linsolve;\r\n\r\n/**\r\n * Square (NxN) matrix.\r\n * \r\n * Implementation notes:\r\n * \r\n * - The storage order is column-major.\r\n *\r\n * - Affine vector of 4x4 matrix is in the 4th column (as in OpenGL).\r\n *\r\n * - Elements are stored in a fixed manner, so it is impossible to change\r\n *   matrix size once it's created.\r\n *\r\n * - Actual data is allocated as a static array, so no references, no GC touching.\r\n *   When you pass a Matrix by value, it will be safely copied.\r\n *\r\n * - This implementation is not perfect (as for now) for dealing with really\r\n *   big matrices, but ideal for smaller ones, e.g. those which are meant to be\r\n *   manipulated in real-time (in game engines, rendering pipelines etc).\r\n */\r\nstruct Matrix(T, size_t N)\r\n{\r\n    /**\r\n     * Compare two matrices.\r\n     *\r\n     * Params:\r\n     *     that = The matrix to compare with.\r\n     *\r\n     * Returns: $(D_KEYWORD true) if dimensions are equal, $(D_KEYWORD false) otherwise.\r\n     */\r\n    bool opEquals(Matrix!(T, N) that) const\r\n    {\r\n        return arrayof == that.arrayof;\r\n    }\r\n\r\n   /**\r\n    * Return zero matrix\r\n    */\r\n    static zero()\r\n    do\r\n    {\r\n        Matrix!(T,N) res;\r\n        foreach (ref v; res.arrayof)\r\n            v = 0;\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * Return identity matrix\r\n    */\r\n    static identity()\r\n    do\r\n    {\r\n        Matrix!(T,N) res;\r\n        res.setIdentity();\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * Set to identity\r\n    */\r\n    void setIdentity()\r\n    do\r\n    {\r\n        foreach(y; 0..N)\r\n        foreach(x; 0..N)\r\n        {\r\n            if (y == x)\r\n                arrayof[y * N + x] = 1;\r\n            else\r\n                arrayof[y * N + x] = 0;\r\n        }\r\n    }\r\n\r\n   /**\r\n    * Create matrix from array.\r\n    * This is a convenient way to deal with arrays of \"classic\" layout:\r\n    * the storage order in an array should be row-major\r\n    */\r\n    this(F)(F[] arr)\r\n    in\r\n    {\r\n        assert (arr.length == N * N,\r\n            \"Matrix!(T,N): wrong array length in constructor\");\r\n    }\r\n    do\r\n    {\r\n        foreach (i, ref v; arrayof)\r\n        {\r\n            auto i2 = i / N + N * (i - N * (i / N));\r\n            v = arr[i2];\r\n        }\r\n    }\r\n\r\n   /**\r\n    * T = Matrix[i, j]\r\n    */\r\n    T opIndex(in size_t i, in size_t j) const\r\n    do\r\n    {\r\n        return arrayof[j * N + i];\r\n    }\r\n\r\n   /**\r\n    * Matrix[i, j] = T\r\n    */\r\n    T opIndexAssign(in T t, in size_t i, in size_t j)\r\n    do\r\n    {\r\n        return (arrayof[j * N + i] = t);\r\n    }\r\n\r\n   /**\r\n    * T = Matrix[index]\r\n    * Indices start with 0\r\n    */\r\n    T opIndex(in size_t index) const\r\n    in\r\n    {\r\n        assert ((0 <= index) && (index < N * N),\r\n            \"Matrix.opIndex(int index): array index out of bounds\");\r\n    }\r\n    do\r\n    {\r\n        return arrayof[index];\r\n    }\r\n\r\n   /**\r\n    * Matrix[index] = T\r\n    * Indices start with 0\r\n    */\r\n    T opIndexAssign(in T t, in size_t index)\r\n    in\r\n    {\r\n        assert ((0 <= index) && (index < N * N),\r\n            \"Matrix.opIndexAssign(T t, int index): array index out of bounds\");\r\n    }\r\n    do\r\n    {\r\n        return (arrayof[index] = t);\r\n    }\r\n\r\n   /**\r\n    * Matrix4x4!(T)[index1..index2] = T\r\n    */\r\n    T[] opSliceAssign(in T t, in size_t index1, in size_t index2)\r\n    in\r\n    {\r\n        assert ((0 <= index1) && (index1 < N * N),\r\n            \"Matrix.opSliceAssign(T t, int index1, int index2): index1 out of bounds\");\r\n        assert ((0 <= index2) && (index2 < N * N),\r\n            \"Matrix.opSliceAssign(T t, int index1, int index2): index2 out of bounds\");\r\n    }\r\n    do\r\n    {\r\n        return (arrayof[index1..index2] = t);\r\n    }\r\n\r\n   /**\r\n    * Matrix[] = T\r\n    */\r\n    T[] opSliceAssign(in T t)\r\n    do\r\n    {\r\n        return (arrayof[] = t);\r\n    }\r\n\r\n   /**\r\n    * Matrix + Matrix\r\n    */\r\n    Matrix!(T,N) opBinary(string op)(Matrix!(T,N) mat) const if (op == \"+\")\r\n    do\r\n    {\r\n        auto res = Matrix!(T,N)();\r\n        foreach (i; 0..N)\r\n        foreach (j; 0..N)\r\n        {\r\n            res[i, j] = this[i, j] + mat[i, j];\r\n        }\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * Matrix - Matrix\r\n    */\r\n    Matrix!(T,N) opBinary(string op)(Matrix!(T,N) mat) const if (op == \"-\")\r\n    do\r\n    {\r\n        auto res = Matrix!(T,N)();\r\n        foreach (i; 0..N)\r\n        foreach (j; 0..N)\r\n        {\r\n            res[i, j] = this[i, j] - mat[i, j];\r\n        }\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * Matrix * Matrix\r\n    */\r\n    Matrix!(T,N) opBinary(string op)(Matrix!(T,N) mat) const if (op == \"*\")\r\n    do\r\n    {\r\n        static if (N == 2)\r\n        {\r\n            Matrix!(T,N) res;\r\n\r\n            res.a11 = (a11 * mat.a11) + (a12 * mat.a21);\r\n            res.a12 = (a11 * mat.a12) + (a12 * mat.a22);\r\n\r\n            res.a21 = (a21 * mat.a11) + (a22 * mat.a21);\r\n            res.a22 = (a21 * mat.a12) + (a22 * mat.a22);\r\n\r\n            return res;\r\n        }\r\n        else static if (N == 3)\r\n        {\r\n            Matrix!(T,N) res;\r\n\r\n            res.a11 = (a11 * mat.a11) + (a12 * mat.a21) + (a13 * mat.a31);\r\n            res.a12 = (a11 * mat.a12) + (a12 * mat.a22) + (a13 * mat.a32);\r\n            res.a13 = (a11 * mat.a13) + (a12 * mat.a23) + (a13 * mat.a33);\r\n\r\n            res.a21 = (a21 * mat.a11) + (a22 * mat.a21) + (a23 * mat.a31);\r\n            res.a22 = (a21 * mat.a12) + (a22 * mat.a22) + (a23 * mat.a32);\r\n            res.a23 = (a21 * mat.a13) + (a22 * mat.a23) + (a23 * mat.a33);\r\n\r\n            res.a31 = (a31 * mat.a11) + (a32 * mat.a21) + (a33 * mat.a31);\r\n            res.a32 = (a31 * mat.a12) + (a32 * mat.a22) + (a33 * mat.a32);\r\n            res.a33 = (a31 * mat.a13) + (a32 * mat.a23) + (a33 * mat.a33);\r\n\r\n            return res;\r\n        }\r\n        else static if (N == 4)\r\n        {\r\n            Matrix!(T,N) res;\r\n\r\n            res.a11 = (a11 * mat.a11) + (a12 * mat.a21) + (a13 * mat.a31) + (a14 * mat.a41);\r\n            res.a12 = (a11 * mat.a12) + (a12 * mat.a22) + (a13 * mat.a32) + (a14 * mat.a42);\r\n            res.a13 = (a11 * mat.a13) + (a12 * mat.a23) + (a13 * mat.a33) + (a14 * mat.a43);\r\n            res.a14 = (a11 * mat.a14) + (a12 * mat.a24) + (a13 * mat.a34) + (a14 * mat.a44);\r\n\r\n            res.a21 = (a21 * mat.a11) + (a22 * mat.a21) + (a23 * mat.a31) + (a24 * mat.a41);\r\n            res.a22 = (a21 * mat.a12) + (a22 * mat.a22) + (a23 * mat.a32) + (a24 * mat.a42);\r\n            res.a23 = (a21 * mat.a13) + (a22 * mat.a23) + (a23 * mat.a33) + (a24 * mat.a43);\r\n            res.a24 = (a21 * mat.a14) + (a22 * mat.a24) + (a23 * mat.a34) + (a24 * mat.a44);\r\n\r\n            res.a31 = (a31 * mat.a11) + (a32 * mat.a21) + (a33 * mat.a31) + (a34 * mat.a41);\r\n            res.a32 = (a31 * mat.a12) + (a32 * mat.a22) + (a33 * mat.a32) + (a34 * mat.a42);\r\n            res.a33 = (a31 * mat.a13) + (a32 * mat.a23) + (a33 * mat.a33) + (a34 * mat.a43);\r\n            res.a34 = (a31 * mat.a14) + (a32 * mat.a24) + (a33 * mat.a34) + (a34 * mat.a44);\r\n\r\n            res.a41 = (a41 * mat.a11) + (a42 * mat.a21) + (a43 * mat.a31) + (a44 * mat.a41);\r\n            res.a42 = (a41 * mat.a12) + (a42 * mat.a22) + (a43 * mat.a32) + (a44 * mat.a42);\r\n            res.a43 = (a41 * mat.a13) + (a42 * mat.a23) + (a43 * mat.a33) + (a44 * mat.a43);\r\n            res.a44 = (a41 * mat.a14) + (a42 * mat.a24) + (a43 * mat.a34) + (a44 * mat.a44);\r\n\r\n            return res;\r\n        }\r\n        else\r\n        {\r\n            auto res = Matrix!(T,N)();\r\n\r\n            foreach (i; 0..N)\r\n            foreach (j; 0..N)\r\n            {\r\n                T sumProduct = 0;\r\n                foreach (k; 0..N)\r\n                    sumProduct += this[i, k] * mat[k, j];\r\n                res[i, j] = sumProduct;\r\n            }\r\n\r\n            return res;\r\n        }\r\n    }\r\n\r\n   /**\r\n    * Matrix += Matrix\r\n    */\r\n    Matrix!(T,N) opOpAssign(string op)(Matrix!(T,N) mat) if (op == \"+\")\r\n    do\r\n    {\r\n        this = this + mat;\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Matrix -= Matrix\r\n    */\r\n    Matrix!(T,N) opOpAssign(string op)(Matrix!(T,N) mat) if (op == \"-\")\r\n    do\r\n    {\r\n        this = this - mat;\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Matrix *= Matrix\r\n    */\r\n    Matrix!(T,N) opOpAssign(string op)(Matrix!(T,N) mat) if (op == \"*\")\r\n    do\r\n    {\r\n        this = this * mat;\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Matrix * T\r\n    */\r\n    Matrix!(T,N) opBinary(string op)(T k) const if (op == \"*\")\r\n    do\r\n    {\r\n        auto res = Matrix!(T,N)();\r\n        foreach(i, v; arrayof)\r\n            res.arrayof[i] = v * k;\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * Matrix *= T\r\n    */\r\n    Matrix!(T,N) opOpAssign(string op)(T k) if (op == \"*\")\r\n    do\r\n    {\r\n        foreach(ref v; arrayof)\r\n            v *= k;\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Multiply column vector by the matrix\r\n    */\r\n    static if (N == 2)\r\n    {\r\n        Vector!(T,2) opBinaryRight(string op) (Vector!(T,2) v) const if (op == \"*\")\r\n        do\r\n        {\r\n            return Vector!(T,2)\r\n            (\r\n                (v.x * a11) + (v.y * a12),\r\n                (v.x * a21) + (v.y * a22)\r\n            );\r\n        }\r\n    }\r\n    else\r\n    static if (N == 3)\r\n    {\r\n        Vector!(T,3) opBinaryRight(string op) (Vector!(T,3) v) const if (op == \"*\")\r\n        do\r\n        {\r\n            return Vector!(T,3)\r\n            (\r\n                (v.x * a11) + (v.y * a12) + (v.z * a13),\r\n                (v.x * a21) + (v.y * a22) + (v.z * a23),\r\n                (v.x * a31) + (v.y * a32) + (v.z * a33)\r\n            );\r\n        }\r\n    }\r\n    else\r\n    {\r\n        Vector!(T,N) opBinaryRight(string op) (Vector!(T,N) v) const if (op == \"*\")\r\n        do\r\n        {\r\n            Vector!(T,N) res;\r\n            foreach(x; 0..N)\r\n            {\r\n                T n = 0;\r\n                foreach(y; 0..N)\r\n                    n += v.arrayof[y] * arrayof[y * N + x];\r\n                res.arrayof[x] = n;\r\n            }\r\n            return res;\r\n        }\r\n    }\r\n\r\n   /**\r\n    * Multiply column 3D vector by the affine 4x4 matrix\r\n    */\r\n    static if (N == 4)\r\n    {\r\n        Vector!(T,3) opBinaryRight(string op) (Vector!(T,3) v) const if (op == \"*\")\r\n        do\r\n        {\r\n            return Vector!(T,3)\r\n            (\r\n                (v.x * a11) + (v.y * a12) + (v.z * a13) + a14,\r\n                (v.x * a21) + (v.y * a22) + (v.z * a23) + a24,\r\n                (v.x * a31) + (v.y * a32) + (v.z * a33) + a34\r\n            );\r\n        }\r\n    }\r\n\r\n    static if (N == 3 || N == 4)\r\n    {\r\n       /**\r\n        * Rotate a vector by the 3x3 upper-left portion of the matrix\r\n        */\r\n        Vector!(T,3) rotate(Vector!(T,3) v) const\r\n        do\r\n        {\r\n            return Vector!(T,3)\r\n            (\r\n                (v.x * a11) + (v.y * a12) + (v.z * a13),\r\n                (v.x * a21) + (v.y * a22) + (v.z * a23),\r\n                (v.x * a31) + (v.y * a32) + (v.z * a33)\r\n            );\r\n        }\r\n\r\n       /**\r\n        * Rotate a vector by the inverse 3x3 upper-left portion of the matrix\r\n        */\r\n        Vector!(T,3) invRotate(Vector!(T,3) v) const\r\n        do\r\n        {\r\n            return Vector!(T,3)\r\n            (\r\n                (v.x * a11) + (v.y * a21) + (v.z * a31),\r\n                (v.x * a12) + (v.y * a22) + (v.z * a32),\r\n                (v.x * a13) + (v.y * a23) + (v.z * a33)\r\n            );\r\n        }\r\n    }\r\n\r\n    static if (N == 4 || N == 3)\r\n    {\r\n        T determinant3x3() const\r\n        do\r\n        {\r\n            return a11 * (a33 * a22 - a32 * a23)\r\n                 - a21 * (a33 * a12 - a32 * a13)\r\n                 + a31 * (a23 * a12 - a22 * a13);\r\n        }\r\n    }\r\n\r\n    static if (N == 1)\r\n    {\r\n       /**\r\n        * Determinant (of upper-left 3x3 portion for 4x4 matrices)\r\n        */\r\n        T determinant() const\r\n        do\r\n        {\r\n            return a11;\r\n        }\r\n    }\r\n    else\r\n    static if (N == 2)\r\n    {\r\n        T determinant() const\r\n        do\r\n        {\r\n            return a11 * a22 - a12 * a21;\r\n        }\r\n    }\r\n    else\r\n    static if (N == 3)\r\n    {\r\n        alias determinant = determinant3x3;\r\n    }\r\n    else\r\n    {\r\n        // Determinant of a given upper-left portion\r\n        T determinant(size_t n = N) const\r\n        do\r\n        {\r\n            T d = 0;\r\n\r\n            if (n == 1)\r\n                d = this[0,0];\r\n            else if (n == 2)\r\n                d = this[0,0] * this[1,1] - this[1,0] * this[0,1];\r\n            else\r\n            {\r\n                auto submat = Matrix!(T,N)();\r\n\r\n                for (uint c = 0; c < n; c++)\r\n                {\r\n                    uint subi = 0;\r\n                    for (uint i = 1; i < n; i++)\r\n                    {\r\n                        uint subj = 0;\r\n                        for (uint j = 0; j < n; j++)\r\n                        {\r\n                            if (j == c)\r\n                                continue;\r\n                            submat[subi, subj] = this[i, j];\r\n                            subj++;\r\n                        }\r\n                        subi++;\r\n                    }\r\n\r\n                    d += pow(-1, c + 2.0) * this[0, c] * submat.determinant(n-1);\r\n                }\r\n            }\r\n\r\n            return d;\r\n        }\r\n    }\r\n\r\n    alias det = determinant;\r\n\r\n   /**\r\n    * Return true if matrix is singular\r\n    */\r\n    bool isSingular() @property\r\n    do\r\n    {\r\n        return (determinant == 0);\r\n    }\r\n\r\n    alias singular = isSingular;\r\n\r\n   /**\r\n    * Check if matrix represents affine transformation\r\n    */\r\n    static if (N == 4)\r\n    {\r\n        bool isAffine() const @property\r\n        do\r\n        {\r\n            return (a41 == 0.0\r\n                 && a42 == 0.0\r\n                 && a43 == 0.0\r\n                 && a44 == 1.0);\r\n        }\r\n\r\n        alias affine = isAffine;\r\n    }\r\n\r\n   /**\r\n    * Transpose\r\n    */\r\n    void transpose()\r\n    do\r\n    {\r\n        this = transposed;\r\n    }\r\n\r\n   /**\r\n    * Return the transposed matrix\r\n    */\r\n    Matrix!(T,N) transposed() @property\r\n    do\r\n    {\r\n        Matrix!(T,N) res;\r\n\r\n        foreach(y; 0..N)\r\n        foreach(x; 0..N)\r\n            res.arrayof[y * N + x] = arrayof[x * N + y];\r\n\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * Invert\r\n    */\r\n    void invert()\r\n    do\r\n    {\r\n        this = inverse;\r\n    }\r\n\r\n   /**\r\n    * Inverse of a matrix\r\n    */\r\n    static if (N == 1)\r\n    {\r\n        Matrix!(T,N) inverse() const @property\r\n        do\r\n        {\r\n            Matrix!(T,N) res;\r\n            res.a11 = 1.0 / a11;\r\n            return res;\r\n        }\r\n    }\r\n    else\r\n    static if (N == 2)\r\n    {\r\n        Matrix!(T,N) inverse() const @property\r\n        do\r\n        {\r\n            Matrix!(T,N) res;\r\n\r\n            T invd = 1.0 / (a11 * a22 - a12 * a21);\r\n\r\n            res.a11 =  a22 * invd;\r\n            res.a12 = -a12 * invd;\r\n            res.a22 =  a11 * invd;\r\n            res.a21 = -a21 * invd;\r\n\r\n            return res;\r\n        }\r\n    }\r\n    else\r\n    static if (N == 3)\r\n    {\r\n        Matrix!(T,N) inverse() const @property\r\n        do\r\n        {\r\n            T d = determinant;\r\n\r\n            T oneOverDet = 1.0 / d;\r\n\r\n            Matrix!(T,N) res;\r\n\r\n            res.a11 =  (a33 * a22 - a32 * a23) * oneOverDet;\r\n            res.a12 = -(a33 * a12 - a32 * a13) * oneOverDet;\r\n            res.a13 =  (a23 * a12 - a22 * a13) * oneOverDet;\r\n\r\n            res.a21 = -(a33 * a21 - a31 * a23) * oneOverDet;\r\n            res.a22 =  (a33 * a11 - a31 * a13) * oneOverDet;\r\n            res.a23 = -(a23 * a11 - a21 * a13) * oneOverDet;\r\n\r\n            res.a31 =  (a32 * a21 - a31 * a22) * oneOverDet;\r\n            res.a32 = -(a32 * a11 - a31 * a12) * oneOverDet;\r\n            res.a33 =  (a22 * a11 - a21 * a12) * oneOverDet;\r\n\r\n            return res;\r\n        }\r\n    }\r\n    else\r\n    {\r\n        Matrix!(T,N) inverse() const @property\r\n        do\r\n        {\r\n            Matrix!(T,N) res;\r\n\r\n            // Inversion via LU decomposition\r\n            enum inv = q{{\r\n                Matrix!(T,N) l, u, p;\r\n                decomposeLUP(this, l, u, p);\r\n                foreach(j; 0..N)\r\n                {\r\n                    Vector!(T,N) b = p.getColumn(j);\r\n                    Vector!(T,N) x;\r\n                    solveLU(l, u, x, b);\r\n                    res.setColumn(j, x);\r\n                }\r\n            }};\r\n\r\n            // Inverse of a 4x4 affine matrix is a special case\r\n            enum affineInv = q{{\r\n                auto m3inv = matrix4x4to3x3(this).inverse;\r\n                res = matrix3x3to4x4(m3inv);\r\n                Vector!(T,3) t = -(getColumn(3).xyz * m3inv);\r\n                res.setColumn(3, Vector!(T,4)(t.x, t.y, t.z, 1.0f));\r\n            }};\r\n\r\n            static if (N == 4)\r\n            {\r\n                if (affine)\r\n                    mixin(affineInv);\r\n                else\r\n                    mixin(inv);\r\n            }\r\n            else\r\n                mixin(inv);\r\n\r\n            return res;\r\n        }\r\n    }\r\n\r\n   /**\r\n    * Adjugate and cofactor matrices\r\n    */\r\n    static if (N == 1)\r\n    {\r\n        Matrix!(T,N) adjugate() @property\r\n        do\r\n        {\r\n            Matrix!(T,N) res;\r\n            res.arrayof[0] = 1;\r\n            return res;\r\n        }\r\n\r\n        Matrix!(T,N) cofactor() @property\r\n        {\r\n            Matrix!(T,N) res;\r\n            res.arrayof[0] = 1;\r\n            return res;\r\n        }\r\n    }\r\n    else\r\n    static if (N == 2)\r\n    {\r\n        Matrix!(T,N) adjugate() @property\r\n        do\r\n        {\r\n            Matrix!(T,N) res;\r\n            res.arrayof[0] =  arrayof[3];\r\n            res.arrayof[1] = -arrayof[1];\r\n            res.arrayof[2] = -arrayof[2];\r\n            res.arrayof[3] =  arrayof[0];\r\n            return res;\r\n        }\r\n\r\n        Matrix!(T,N) cofactor() @property\r\n        {\r\n            Matrix!(T,N) res;\r\n            res.arrayof[0] =  arrayof[3];\r\n            res.arrayof[1] = -arrayof[2];\r\n            res.arrayof[2] = -arrayof[1];\r\n            res.arrayof[3] =  arrayof[0];\r\n            return res;\r\n        }\r\n    }\r\n    else\r\n    {\r\n        Matrix!(T,N) adjugate() @property\r\n        do\r\n        {\r\n            return cofactor.transposed;\r\n        }\r\n\r\n        Matrix!(T,N) cofactor() @property\r\n        do\r\n        {\r\n            Matrix!(T,N) res;\r\n\r\n            foreach(y; 0..N)\r\n            foreach(x; 0..N)\r\n            {\r\n                auto submat = Matrix!(T,N-1)();\r\n\r\n                uint suby = 0;\r\n                foreach(yy; 0..N)\r\n                if (yy != y)\r\n                {\r\n                    uint subx = 0;\r\n                    foreach(xx; 0..N)\r\n                    if (xx != x)\r\n                    {\r\n                        submat[subx, suby] = this[xx, yy];\r\n                        subx++;\r\n                    }\r\n                    suby++;\r\n                }\r\n\r\n                res[x, y] = submat.determinant * (((x + y) % 2)? -1:1);\r\n            }\r\n\r\n            return res;\r\n        }\r\n    }\r\n\r\n   /**\r\n    * Negative matrix\r\n    */\r\n    Matrix!(T,N) negative() @property\r\n    do\r\n    {\r\n        return this * -1;\r\n    }\r\n\r\n   /**\r\n    * Convert to string\r\n    */\r\n    string toString() @property\r\n    do\r\n    {\r\n        return matrixToStr(this);\r\n    }\r\n\r\n   /**\r\n    * Symbolic element access\r\n    */\r\n    private static string elements(string letter) @property\r\n    do\r\n    {\r\n        string res;\r\n        foreach (x; 0..N)\r\n        foreach (y; 0..N)\r\n        {\r\n            res ~= \"T \" ~ letter ~ to!string(y+1) ~ to!string(x+1) ~ \";\";\r\n        }\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * Row/column manipulations\r\n    */\r\n    Vector!(T,N) getRow(size_t i)\r\n    {\r\n        Vector!(T,N) res;\r\n        for (size_t j = 0; j < N; j++)\r\n            res.arrayof[j] = this[i, j];\r\n        return res;\r\n    }\r\n\r\n    void setRow(size_t i, Vector!(T,N) v)\r\n    {\r\n        for (size_t j = 0; j < N; j++)\r\n            this[i, j] = v.arrayof[j];\r\n    }\r\n\r\n    Vector!(T,N) getColumn(size_t j) const\r\n    {\r\n        Vector!(T,N) res;\r\n        for (size_t i = 0; i < N; i++)\r\n            res.arrayof[i] = this[i, j];\r\n        return res;\r\n    }\r\n\r\n    void setColumn(size_t j, Vector!(T,N) v)\r\n    {\r\n        for (size_t i = 0; i < N; i++)\r\n            this[i, j] = v.arrayof[i];\r\n    }\r\n\r\n    void swapRows(size_t r1, size_t r2)\r\n    {\r\n        for (size_t j = 0; j < N; j++)\r\n        {\r\n            T t = this[r1, j];\r\n            this[r1, j] = this[r2, j];\r\n            this[r2, j] = t;\r\n        }\r\n    }\r\n\r\n    void swapColumns(size_t c1, size_t c2)\r\n    {\r\n        for (size_t i = 0; i < N; i++)\r\n        {\r\n            T t = this[i, c1];\r\n            this[i, c1] = this[i, c2];\r\n            this[i, c2] = t;\r\n        }\r\n    }\r\n\r\n    auto flatten()\r\n    {\r\n        return transposed.arrayof;\r\n    }\r\n\r\n   /**\r\n    * Matrix elements\r\n    */\r\n    union\r\n    {\r\n       /**\r\n        * This auto-generated structure provides symbolic access\r\n        * to matrix elements, like in standard mathematic notation:\r\n        * ---\r\n        *  a11 a12 a13 a14 .. a1N\r\n        *  a21 a22 a23 a24 .. a2N\r\n        *  a31 a32 a33 a34 .. a3N\r\n        *  a41 a42 a43 a44 .. a4N\r\n        *   :   :   :   :  .\r\n        *  aN1 aN2 aN3 aN4  ' aNN\r\n        * ---\r\n        */\r\n        struct { mixin(elements(\"a\")); }\r\n\r\n       /**\r\n        * Linear array representing elements column by column\r\n        */\r\n        T[N * N] arrayof;\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    auto m1 = matrixf(\r\n        1, 2, 0, 6,\r\n        4, 6, 3, 1,\r\n        2, 7, 8, 2,\r\n        0, 5, 2, 1\r\n    );\r\n    auto m2 = matrixf(\r\n        0, 3, 7, 1,\r\n        1, 0, 2, 5,\r\n        1, 9, 2, 6,\r\n        5, 2, 0, 0\r\n    );\r\n    assert(m1 * m2 == matrixf(\r\n        32, 15, 11, 11,\r\n        14, 41, 46, 52,\r\n        25, 82, 44, 85,\r\n        12, 20, 14, 37)\r\n    );\r\n\r\n    auto m3 = Matrix4f.identity;\r\n    assert(m3 == matrixf(\r\n        1, 0, 0, 0,\r\n        0, 1, 0, 0,\r\n        0, 0, 1, 0,\r\n        0, 0, 0, 1)\r\n    );\r\n\r\n    m3[12] = 1;\r\n    m3.a24 = 2;\r\n    m3.a34 = 3;\r\n    m3[1..4] = 0;\r\n    \r\n    assert(m3[12] == 1);\r\n\r\n    assert(m1.determinant3x3 == -25);\r\n    assert(m1.determinant == 567);\r\n\r\n    assert(m1.singular == false);\r\n\r\n    assert(m1.affine == false);\r\n\r\n    assert(m1.transposed == matrixf(\r\n        1, 4, 2, 0,\r\n        2, 6, 7, 5,\r\n        0, 3, 8, 2,\r\n        6, 1, 2, 1)\r\n    );\r\n\r\n    auto m4 = matrixf(\r\n        0, 3, 2,\r\n        1, 0, 8,\r\n        0, 1, 0\r\n    );\r\n\r\n    assert(m4.inverse == matrixf(\r\n        -4,   1, 12,\r\n        -0,   0,  1,\r\n         0.5, 0, -1.5)\r\n    );\r\n    \r\n    assert(m1.adjugate == matrixf(\r\n        7, 148, -16, -158,\r\n      -14,  28, -49,  154,\r\n      -14, -53, 113,  -89,\r\n       98, -34,  19,  -25)\r\n    );\r\n\r\n    assert(m1.cofactor == matrixf(\r\n        7, -14, -14,  98,\r\n      148,  28, -53, -34,\r\n      -16, -49, 113,  19,\r\n     -158, 154, -89, -25)\r\n    );\r\n    \r\n    m1.transpose();\r\n    assert(m1 == matrixf(\r\n        1, 4, 2, 0,\r\n        2, 6, 7, 5,\r\n        0, 3, 8, 2,\r\n        6, 1, 2, 1)\r\n    );\r\n    \r\n    Matrix2f m5;\r\n    m5[] = 1.0f;\r\n    m5 += matrixf(\r\n      2, 2,\r\n      2, 2\r\n    );\r\n    m5 *= m5;\r\n    assert(m5 == matrixf(\r\n      18, 18,\r\n      18, 18)\r\n    );\r\n    \r\n    m5 = m5 - m5;\r\n    assert(m5 == Matrix2f.zero);\r\n    \r\n    Matrix2f m6 = matrixf(\r\n      2, 2,\r\n      2, 2\r\n    );\r\n    m6 = m6 * m6;\r\n    m6 = m6 * 2;\r\n    m6 *= 2;\r\n    assert(m6 == matrixf(\r\n      32, 32,\r\n      32, 32)\r\n    );\r\n    assert(m6.determinant == 0);\r\n    \r\n    Matrix3f m7 = matrixf(\r\n      3, 3, 3,\r\n      3, 3, 3,\r\n      3, 3, 3\r\n    );\r\n    m7 = m7 * m7;\r\n    assert(m7 == matrixf(\r\n      27, 27, 27,\r\n      27, 27, 27,\r\n      27, 27, 27)\r\n    );\r\n    \r\n    Matrix2f m8 = matrixf(\r\n        1, 0,\r\n        0, 1\r\n    );\r\n    m8.invert();\r\n    assert(m8 == matrixf(\r\n        1, 0,\r\n        0, 1)\r\n    );\r\n    assert(m8.negative == matrixf(\r\n       -1,  0,\r\n        0, -1)\r\n    );\r\n    \r\n    auto m9 = matrixf(\r\n        1, 0, 0, 2,\r\n        0, 1, 0, 3,\r\n        0, 0, 1, 4,\r\n        0, 0, 0, 1\r\n    );\r\n    assert(m9.affine == true);\r\n    assert(m9.inverse == matrixf(\r\n        1, 0, 0, -2,\r\n        0, 1, 0, -3,\r\n        0, 0, 1, -4,\r\n        0, 0, 0,  1)\r\n    );\r\n    \r\n    bool isAlmostZero3(Vector3f v)\r\n    {\r\n        float e = 0.002f;\r\n        return abs(v.x) < e &&\r\n               abs(v.y) < e &&\r\n               abs(v.z) < e;\r\n    }\r\n    \r\n    Vector3f v1 = Vector3f(1, 0, 0);\r\n    v1 = v1 * matrixf(\r\n        1, 0, 0, 2,\r\n        0, 1, 0, 3,\r\n        0, 0, 1, 4,\r\n        0, 0, 0, 1\r\n    );\r\n    assert(v1 == Vector3f(3, 3, 4));\r\n    \r\n    Vector3f v2 = Vector3f(0, 1, 0);\r\n    const float a1 = PI * 0.5f;\r\n    v2 = matrixf(\r\n        1, 0,        0,       0,\r\n        0, cos(a1), -sin(a1), 0,\r\n        0, sin(a1),  cos(a1), 0,\r\n        0, 0,        0,       1\r\n    ).rotate(v2);\r\n    assert(isAlmostZero3(v2 - Vector3f(0, 0, 1)));\r\n    \r\n    Vector3f v3 = Vector3f(0, 1, 0);\r\n    v3 = matrixf(\r\n        1, 0,        0,       0,\r\n        0, cos(a1), -sin(a1), 0,\r\n        0, sin(a1),  cos(a1), 0,\r\n        0, 0,        0,       1\r\n    ).invRotate(v3);\r\n    assert(isAlmostZero3(v3 - Vector3f(0, 0, -1)));\r\n    \r\n    auto m10 = matrixf(\r\n        1, 2, 3,\r\n        3, 2, 1,\r\n        2, 3, 1\r\n    );\r\n    \r\n    Vector3f r0 = m10.getRow(0);\r\n    assert(isAlmostZero3(r0 - Vector3f(1, 2, 3)));\r\n    \r\n    Vector3f c0 = m10.getColumn(0);\r\n    assert(isAlmostZero3(c0 - Vector3f(1, 3, 2)));\r\n    \r\n    m10.setRow(2, Vector3f(1, 1, 1));\r\n    Vector3f r2 = m10.getRow(2);\r\n    assert(isAlmostZero3(r2 - Vector3f(1, 1, 1)));\r\n    \r\n    m10.setColumn(2, Vector3f(1, 1, 1));\r\n    Vector3f c2 = m10.getColumn(2);\r\n    assert(isAlmostZero3(c2 - Vector3f(1, 1, 1)));\r\n    \r\n    m10.swapRows(0, 1);\r\n    Vector3f r1 = m10.getRow(1);\r\n    assert(isAlmostZero3(r1 - Vector3f(1, 2, 1)));\r\n    \r\n    m10.swapColumns(1, 2);\r\n    Vector3f c1 = m10.getColumn(1);\r\n    assert(isAlmostZero3(c1 - Vector3f(1, 1, 1)));\r\n    \r\n    Matrix2f m11 = matrixf(\r\n        2, 1,\r\n        2, 1\r\n    );\r\n    assert(m11.adjugate == matrixf(\r\n        1, -1,\r\n       -2,  2)\r\n    );\r\n    assert(m11.cofactor == matrixf(\r\n        1, -2,\r\n       -1,  2)\r\n    );\r\n    assert(m11.flatten == [2, 1, 2, 1]);\r\n    \r\n    assert(m11.elements(\"a\") == \"T a11;T a21;T a12;T a22;\");\r\n    \r\n    Matrix!(float, 1) m12 = matrixf(1);\r\n    assert(m12.determinant == 1);\r\n    assert(m12.inverse == matrixf(1));\r\n    assert(m12.adjugate == matrixf(1));\r\n    assert(m12.cofactor == matrixf(1));\r\n}\r\n\r\n/*\r\n * Predefined matrix type aliases\r\n */\r\n/// Alias for single precision 2x2 Matrix\r\nalias Matrix!(float, 2) Matrix2x2f, Matrix2f;\r\n/// Alias for single precision 3x3 Matrix\r\nalias Matrix!(float, 3) Matrix3x3f, Matrix3f;\r\n/// Alias for single precision 4x4 Matrix\r\nalias Matrix!(float, 4) Matrix4x4f, Matrix4f;\r\n/// Alias for double precision 2x2 Matrix\r\nalias Matrix!(double, 2) Matrix2x2d, Matrix2d;\r\n/// Alias for double precision 3x3 Matrix\r\nalias Matrix!(double, 3) Matrix3x3d, Matrix3d;\r\n/// Alias for double precision 4x4 Matrix\r\nalias Matrix!(double, 4) Matrix4x4d, Matrix4d;\r\n\r\n/**\r\n * Short aliases\r\n */\r\nalias mat2 = Matrix2x2f;\r\nalias mat3 = Matrix3x3f;\r\nalias mat4 = Matrix4x4f;\r\n\r\n/**\r\n * Matrix factory function\r\n */\r\nauto matrixf(A...)(A arr)\r\n{\r\n    static assert(isPerfectSquare(arr.length),\r\n        \"matrixf(A): input array length is not perfect square integer\");\r\n    return Matrix!(float, cast(size_t)sqrt(cast(float)arr.length))([arr]);\r\n}\r\n\r\n/**\r\n * Converts 3x3 matrix to 4x4 matrix.\r\n * 4x4 matrix defaults to identity\r\n */\r\nMatrix!(T,4) matrix3x3to4x4(T) (Matrix!(T,3) m)\r\n{\r\n    auto res = Matrix!(T,4).identity;\r\n    res.a11 = m.a11; res.a12 = m.a12; res.a13 = m.a13;\r\n    res.a21 = m.a21; res.a22 = m.a22; res.a23 = m.a23;\r\n    res.a31 = m.a31; res.a32 = m.a32; res.a33 = m.a33;\r\n    return res;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Matrix3f m1 = matrixf(\r\n        1, 0, 0,\r\n        0, 1, 0,\r\n        0, 0, 1\r\n    );\r\n    Matrix4f m2 = matrix3x3to4x4(m1);\r\n    assert(m2 == matrixf(\r\n        1, 0, 0, 0,\r\n        0, 1, 0, 0,\r\n        0, 0, 1, 0,\r\n        0, 0, 0, 1)\r\n    );\r\n}\r\n\r\n/**\r\n * Converts 4x4 matrix to 3x3 matrix.\r\n * 3x3 matrix defaults to identity\r\n */\r\nMatrix!(T,3) matrix4x4to3x3(T) (Matrix!(T,4) m)\r\n{\r\n    auto res = Matrix!(T,3).identity;\r\n    res.a11 = m.a11; res.a12 = m.a12; res.a13 = m.a13;\r\n    res.a21 = m.a21; res.a22 = m.a22; res.a23 = m.a23;\r\n    res.a31 = m.a31; res.a32 = m.a32; res.a33 = m.a33;\r\n    return res;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Matrix4f m1 = matrixf(\r\n        1, 0, 0, 0,\r\n        0, 1, 0, 0,\r\n        0, 0, 1, 0,\r\n        0, 0, 0, 1\r\n    );\r\n    Matrix3f m2 = matrix4x4to3x3(m1);\r\n    assert(m2 == matrixf(\r\n        1, 0, 0,\r\n        0, 1, 0,\r\n        0, 0, 1)\r\n    );\r\n}\r\n\r\n/**\r\n * Formatted matrix printer\r\n */\r\nstring matrixToStr(T, size_t N)(Matrix!(T, N) m)\r\n{\r\n    uint width = 8;\r\n    string maxnum;\r\n    foreach(x; m.arrayof)\r\n    {\r\n        string num;\r\n        real frac, integ;\r\n        frac = modf(x, integ);\r\n        if (frac == 0.0f)\r\n        {\r\n            num = format(\"% s\", to!long(integ));\r\n            if (num.length > width)\r\n                width = cast(uint)num.length;\r\n        }\r\n        else\r\n        {\r\n            num = format(\"% .4f\", x);\r\n        }\r\n    }\r\n\r\n    auto writer = appender!string();\r\n    foreach (x; 0..N)\r\n    {\r\n        foreach (y; 0..N)\r\n        {\r\n            string s = format(\"% -*.4f\", width, m.arrayof[y * N + x]);\r\n            uint n = 0;\r\n            foreach(i, c; s)\r\n            {\r\n                if (i < width)\r\n                {\r\n                    formattedWrite(writer, c.to!string);\r\n                    n++;\r\n                }\r\n            }\r\n\r\n            if (y < N-1)\r\n                formattedWrite(writer, \"  \");\r\n        }\r\n\r\n        if (x < N-1)\r\n            formattedWrite(writer, \"\\n\");\r\n    }\r\n\r\n    return writer.data;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    import std.string;\r\n    Matrix2f m1 = matrixf(\r\n        1, 0,\r\n        0, 1\r\n    );\r\n    string s = m1.toString;\r\n    assert(s.startsWith(\" 1.0000    0.0000 \\n 0.0000    1.0000\"));\r\n}\r\n"
  },
  {
    "path": "dlib/math/package.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Linear algebra and calculus\r\n *\r\n * Description:\r\n * dlib.math brings vector and matrix types to D, as well as some calculus \r\n * functionality. dlib.math is great as a math library for games, \r\n * graphics/physics engines and rendering pipelines. All types are POD and \r\n * OpenGL-friendly: you can pass your 4x4 matrices to OpenGL functions directly, \r\n * without any conversion.\r\n *\r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.math;\r\n\r\npublic\r\n{\r\n    import dlib.math.combinatorics;\r\n    import dlib.math.complex;\r\n    import dlib.math.decomposition;\r\n    import dlib.math.diff;\r\n    import dlib.math.dual;\r\n    import dlib.math.dualquaternion;\r\n    import dlib.math.fft;\r\n    import dlib.math.hof;\r\n    import dlib.math.interpolation;\r\n    import dlib.math.linsolve;\r\n    import dlib.math.matrix;\r\n    import dlib.math.quaternion;\r\n    import dlib.math.sse;\n    import dlib.math.tensor;\n    import dlib.math.transformation;\r\n    import dlib.math.utils;\r\n    import dlib.math.vector;\r\n}\r\n"
  },
  {
    "path": "dlib/math/quaternion.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Quaternions\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.math.quaternion;\r\n\r\nimport std.math;\r\nimport std.traits;\r\n\r\nimport dlib.math.vector;\r\nimport dlib.math.matrix;\r\nimport dlib.math.utils;\r\n\r\n/**\r\n * Quaternion representation\r\n */\r\nstruct Quaternion(T)\r\n{\r\n    Vector!(T,4) vectorof;\r\n    alias vectorof this;\r\n\r\n    this(T x, T y, T z, T w)\r\n    {\r\n        vectorof = Vector!(T,4)(x, y, z, w);\r\n    }\r\n\r\n    this(T[4] arr)\r\n    {\r\n        vectorof.arrayof = arr;\r\n    }\r\n\r\n    this(Vector!(T,4) v)\r\n    {\r\n        vectorof = v;\r\n    }\r\n\r\n    this(Vector!(T,3) v, T neww)\r\n    {\r\n        vectorof = Vector!(T,4)(v.x, v.y, v.z, neww);\r\n    }\r\n\r\n   /**\r\n    * Identity quaternion\r\n    */\r\n    static Quaternion!(T) identity()\r\n    {\r\n        return Quaternion!(T)(T(0), T(0), T(0), T(1));\r\n    }\r\n\r\n   /**\r\n    * Quaternion!(T) + Quaternion!(T)\r\n    */\r\n    Quaternion!(T) opBinary(string op)(Quaternion!(T) q) if (op == \"+\")\r\n    {\r\n        return Quaternion!(T)(x + q.x, y + q.y, z + q.z, w + q.w);\r\n    }\r\n\r\n   /**\r\n    * Quaternion!(T) += Quaternion!(T)\r\n    */\r\n    Quaternion!(T) opOpAssign(string op)(Quaternion!(T) q) if (op == \"+\")\r\n    {\r\n        this = this + q;\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Quaternion!(T) - Quaternion!(T)\r\n    */\r\n    Quaternion!(T) opBinary(string op)(Quaternion!(T) q) if (op == \"-\")\r\n    {\r\n        return Quaternion!(T)(x - q.x, y - q.y, z - q.z, w - q.w);\r\n    }\r\n\r\n   /**\r\n    * Quaternion!(T) -= Quaternion!(T)\r\n    */\r\n    Quaternion!(T) opOpAssign(string op)(Quaternion!(T) q) if (op == \"-\")\r\n    {\r\n        this = this - q;\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Quaternion!(T) * Quaternion!(T)\r\n    */\r\n    Quaternion!(T) opBinary(string op)(Quaternion!(T) q) if (op == \"*\")\r\n    {\r\n        return Quaternion!(T)\r\n        (\r\n            (x * q.w) + (w * q.x) + (y * q.z) - (z * q.y),\r\n            (y * q.w) + (w * q.y) + (z * q.x) - (x * q.z),\r\n            (z * q.w) + (w * q.z) + (x * q.y) - (y * q.x),\r\n            (w * q.w) - (x * q.x) - (y * q.y) - (z * q.z)\r\n        );\r\n    }\r\n\r\n   /**\r\n    * Quaternion!(T) *= Quaternion!(T)\r\n    */\r\n    Quaternion!(T) opOpAssign(string op)(Quaternion!(T) q) if (op == \"*\")\r\n    {\r\n        this = this * q;\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Quaternion!(T) * T\r\n    */\r\n    Quaternion!(T) opBinary(string op)(T k) if (op == \"*\")\r\n    {\r\n        return Quaternion!(T)(x * k, y * k, z * k, w * k);\r\n    }\r\n\r\n   /**\r\n    * Quaternion!(T) *= T\r\n    */\r\n    Quaternion!(T) opOpAssign(string op)(T k) if (op == \"*\")\r\n    {\r\n        x *= k;\r\n        y *= k;\r\n        z *= k;\r\n        w *= k;\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * T * Quaternion!(T)\r\n    */\r\n    Quaternion!(T) opBinaryRight(string op) (T k) if (op == \"*\")\r\n    {\r\n        return Quaternion!(T)(x * k, y * k, z * k, w * k);\r\n    }\r\n\r\n   /**\r\n    * Quaternion!(T) / T\r\n    */\r\n    Quaternion!(T) opBinary(string op)(T k) if (op == \"/\")\r\n    {\r\n        T oneOverK = 1.0 / k;\r\n        return Quaternion!(T)\r\n        (\r\n            x * oneOverK,\r\n            y * oneOverK,\r\n            z * oneOverK,\r\n            w * oneOverK\r\n        );\r\n    }\r\n\r\n   /**\r\n    * Quaternion!(T) /= T\r\n    */\r\n    Quaternion!(T) opOpAssign(string op)(T k) if (op == \"/\")\r\n    {\r\n        T oneOverK = 1.0 / k;\r\n        x *= oneOverK;\r\n        y *= oneOverK;\r\n        z *= oneOverK;\r\n        w *= oneOverK;\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Quaternion!(T) * Vector!(T,3)\r\n    */\r\n    Quaternion!(T) opBinary(string op)(Vector!(T,3) v) if (op == \"*\")\r\n    {\r\n        return Quaternion!(T)\r\n        (\r\n            (w * v.x) + (y * v.z) - (z * v.y),\r\n            (w * v.y) + (z * v.x) - (x * v.z),\r\n            (w * v.z) + (x * v.y) - (y * v.x),\r\n          - (x * v.x) - (y * v.y) - (z * v.z)\r\n        );\r\n    }\r\n\r\n   /**\r\n    * Quaternion!(T) *= Vector!(T,3)\r\n    */\r\n    Quaternion!(T) opOpAssign(string op)(Vector!(T,3) v) if (op == \"*\")\r\n    {\r\n        this = this * v;\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Conjugate\r\n    * A quaternion with the opposite rotation\r\n    */\r\n    Quaternion!(T) conjugate()\r\n    {\r\n        return Quaternion!(T)(-x, -y, -z, w);\r\n    }\r\n\r\n    alias conj = conjugate;\r\n\r\n   /**\r\n    * Compute the W component of a unit length quaternion\r\n    */\r\n    void computeW()\r\n    {\r\n        T t = T(1.0) - (x * x) - (y * y) - (z * z);\r\n        if (t < 0.0)\r\n            w = 0.0;\r\n        else\r\n            w = -(t.sqrt);\r\n    }\r\n\r\n   /**\r\n    * Rotate a point by quaternion\r\n    */\r\n    Vector!(T,3) rotate(Vector!(T,3) v)\r\n    {\r\n        Quaternion!(T) qf = this * v * this.conj;\r\n        return Vector!(T,3)(qf.x, qf.y, qf.z);\r\n    }\r\n\r\n    Vector!(T,3) opBinaryRight(string op) (Vector!(T,3) v) if (op == \"*\")\r\n    {\r\n        Quaternion!(T) qf = this * v * this.conj;\r\n        return Vector!(T,3)(qf.x, qf.y, qf.z);\r\n    }\r\n\r\n    static if (isNumeric!(T))\r\n    {\r\n       /**\r\n        * Normalized version\r\n        */\r\n        Quaternion!(T) normalized()\r\n        {\r\n            Quaternion!(T) q = this;\r\n            q.normalize();\r\n            return q;\r\n        }\r\n\r\n       /**\r\n        * Convert to 4x4 matrix\r\n        */\r\n        Matrix!(T,4) toMatrix4x4() const\r\n        {\r\n            auto mat = Matrix!(T,4).identity;\r\n\r\n            mat[0]  = 1.0 - 2.0 * (y * y + z * z);\r\n            mat[1]  = 2.0 * (x * y + z * w);\r\n            mat[2]  = 2.0 * (x * z - y * w);\r\n            mat[3]  = 0.0;\r\n\r\n            mat[4]  = 2.0 * (x * y - z * w);\r\n            mat[5]  = 1.0 - 2.0 * (x * x + z * z);\r\n            mat[6]  = 2.0 * (z * y + x * w);\r\n            mat[7]  = 0.0;\r\n\r\n            mat[8]  = 2.0 * (x * z + y * w);\r\n            mat[9]  = 2.0 * (y * z - x * w);\r\n            mat[10] = 1.0 - 2.0 * (x * x + y * y);\r\n            mat[11] = 0.0;\r\n\r\n            mat[12] = 0.0;\r\n            mat[13] = 0.0;\r\n            mat[14] = 0.0;\r\n            mat[15] = 1.0;\r\n\r\n            return mat;\r\n        }\r\n\r\n       /**\r\n        * Convert to 3x3 matrix\r\n        */\r\n        Matrix!(T,3) toMatrix3x3() const\r\n        {\r\n            auto mat = Matrix!(T,3).identity;\r\n\r\n            mat[0] = 1.0 - 2.0 * (y * y + z * z);\r\n            mat[1] = 2.0 * (x * y + z * w);\r\n            mat[2] = 2.0 * (x * z - y * w);\r\n\r\n            mat[3] = 2.0 * (x * y - z * w);\r\n            mat[4] = 1.0 - 2.0 * (x * x + z * z);\r\n            mat[5] = 2.0 * (z * y + x * w);\r\n\r\n            mat[6] = 2.0 * (x * z + y * w);\r\n            mat[7] = 2.0 * (y * z - x * w);\r\n            mat[8] = 1.0 - 2.0 * (x * x + y * y);\r\n\r\n            return mat;\r\n        }\r\n\r\n       /**\r\n        * Setup the quaternion to perform a rotation,\r\n        * given the angular displacement in matrix form\r\n        */\r\n        static Quaternion!(T) fromMatrix(Matrix!(T,4) m)\r\n        {\r\n            Quaternion!(T) q;\r\n\r\n            T trace = m.a11 + m.a22 + m.a33 + 1.0;\r\n\r\n            if (trace > 0.0001)\r\n            {\r\n                T s = 0.5 / sqrt(trace);\r\n                q.w = 0.25 / s;\r\n                q.x = (m.a23 - m.a32) * s;\r\n                q.y = (m.a31 - m.a13) * s;\r\n                q.z = (m.a12 - m.a21) * s;\r\n            }\r\n            else\r\n            {\r\n                if ((m.a11 > m.a22) && (m.a11 > m.a33))\r\n                {\r\n                    T s = 0.5 / sqrt(1.0 + m.a11 - m.a22 - m.a33);\r\n                    q.x = 0.25 / s;\r\n                    q.y = (m.a21 + m.a12) * s;\r\n                    q.z = (m.a31 + m.a13) * s;\r\n                    q.w = (m.a32 - m.a23) * s;\r\n                }\r\n                else if (m.a22 > m.a33)\r\n                {\r\n                    T s = 0.5 / sqrt(1.0 + m.a22 - m.a11 - m.a33);\r\n                    q.x = (m.a21 + m.a12) * s;\r\n                    q.y = 0.25 / s;\r\n                    q.z = (m.a32 + m.a23) * s;\r\n                    q.w = (m.a31 - m.a13) * s;\r\n                }\r\n                else\r\n                {\r\n                    T s = 0.5 / sqrt(1.0 + m.a33 - m.a11 - m.a22);\r\n                    q.x = (m.a31 + m.a13) * s;\r\n                    q.y = (m.a32 + m.a23) * s;\r\n                    q.z = 0.25 / s;\r\n                    q.w = (m.a21 - m.a12) * s;\r\n                }\r\n            }\r\n\r\n            return q;\r\n        }\r\n\r\n       /**\r\n        * Setup the quaternion to perform a rotation,\r\n        * given the orientation in Euler angles pitch, yaw, roll (in radians)\r\n        */\r\n        static Quaternion!(T) fromEulerAngles(Vector!(T,3) e)\r\n        {\r\n            Quaternion!(T) q;\r\n            \r\n            T cy = cos(e.z * 0.5);\r\n            T sy = sin(e.z * 0.5);\r\n            T cp = cos(e.y * 0.5);\r\n            T sp = sin(e.y * 0.5);\r\n            T cr = cos(e.x * 0.5);\r\n            T sr = sin(e.x * 0.5);\r\n\r\n            q.w = cr * cp * cy + sr * sp * sy;\r\n            q.x = sr * cp * cy - cr * sp * sy;\r\n            q.y = cr * sp * cy + sr * cp * sy;\r\n            q.z = cr * cp * sy - sr * sp * cy;\r\n\r\n            return q;\r\n        }\r\n\r\n       /**\r\n        * Setup the Euler angles (pitch, yaw, roll), given a rotation Quaternion.\r\n        * Returned x, y, z are in radians\r\n        */\r\n        Vector!(T,3) toEulerAngles()\r\n        {\r\n            Vector!(T,3) e;\r\n            \r\n            // pitch (x-axis rotation)\r\n            T sinr_cosp = 2 * (w * x + y * z);\r\n            T cosr_cosp = 1 - 2 * (x * x + y * y);\r\n            e.x = atan2(sinr_cosp, cosr_cosp);\r\n            \r\n            // yaw (y-axis rotation)\r\n            T sinp = 2 * (w * y - z * x);\r\n            if (abs(sinp) >= 1.0)\r\n                e.y = PI / 2.0 * sinp;\r\n            else\r\n                e.y = asin(sinp);\r\n            \r\n            // roll (z-axis rotation)\r\n            T siny_cosp = 2 * (w * z + x * y);\r\n            T cosy_cosp = 1 - 2 * (y * y + z * z);\r\n            e.z = atan2(siny_cosp, cosy_cosp);\r\n            \r\n            return e;\r\n        }\r\n\r\n       /**\r\n        * Return the rotation angle (in radians)\r\n        */\r\n        T rotationAngle()\r\n        {\r\n            return 2.0 * acos(w);\r\n        }\r\n\r\n       /**\r\n        * Return the rotation axis\r\n        */\r\n        Vector!(T,3) rotationAxis()\r\n        {\r\n            T s = sqrt(1.0 - (w * w));\r\n\r\n            if (s <= 0.0f)\r\n                return Vector!(T,3)(x, y, z);\r\n            else\r\n                return Vector!(T,3)(x / s, y / s, z / s);\r\n        }\r\n    }\r\n}\r\n\r\n/*\r\n * Predefined quaternion type aliases\r\n */\r\n/// Alias for single precision Quaternion\r\nalias Quaternionf = Quaternion!(float);\r\n/// Alias for double precision Quaternion\r\nalias Quaterniond = Quaternion!(double);\r\n\r\n///\r\nunittest\r\n{\r\n    import dlib.math.transformation;\r\n    \r\n    Quaternionf q1 = Quaternionf(0.0f, 0.0f, 0.0f, 1.0f);\r\n    Vector3f v1 = q1.rotate(Vector3f(1.0f, 0.0f, 0.0f));\r\n    assert(isAlmostZero(v1 - Vector3f(1.0f, 0.0f, 0.0f)));\r\n    \r\n    Quaternionf q2 = Quaternionf.identity;\r\n    assert(isConsiderZero(q2.x));\r\n    assert(isConsiderZero(q2.y));\n    assert(isConsiderZero(q2.z));\r\n    assert(isConsiderZero(q2.w - 1.0f));\r\n    \r\n    Quaternionf q3 = Quaternionf([1.0f, 0.0f, 0.0f, 1.0f]);\r\n    Quaternionf q4 = Quaternionf([0.0f, 1.0f, 0.0f, 1.0f]);\r\n    q4 = q3 * q4;\r\n    assert(q4 == Quaternionf(1, 1, 1, 1));\r\n    \r\n    Vector3f v2 = Vector3f(0, 0, 1);\r\n    Quaternionf q5 = Quaternionf(v2, 1.0f);\r\n    q5 *= q5;\r\n    assert(q5 == Quaternionf(0, 0, 2, 0));\r\n    \r\n    Quaternionf q6 = Quaternionf(Vector4f(1, 0, 0, 1));\r\n    Quaternionf q7 = q6 + q6 - Quaternionf(2, 0, 0, 2);\r\n    assert(q7 == Quaternionf(0, 0, 0, 0));\r\n    \r\n    Quaternionf q8 = Quaternionf(0.5f, 0.5f, 0.5f, 0.0f);\r\n    q8.computeW();\r\n    assert(q8.w == -0.5f);\r\n    \r\n    Quaternionf q9 = Quaternionf(0.5f, 0.0f, 0.0f, 0.0f);\r\n    q9 = q9.normalized;\r\n    assert(q9 == Quaternionf(1, 0, 0, 0));\r\n    \r\n    assert(q9 * 2.0f == Quaternionf(2, 0, 0, 0));\r\n    assert(3.0f * q9 == Quaternionf(3, 0, 0, 0));\r\n    assert(q9 / 2.0f == Quaternionf(0.5f, 0, 0, 0));\r\n    \r\n    Quaternionf q10 = Quaternionf(0.0f, 0.0f, 0.0f, 1.0f);\r\n    Matrix4f m1 = q10.toMatrix4x4;\r\n    assert(m1 == matrixf(\r\n        1, 0, 0, 0,\r\n        0, 1, 0, 0,\r\n        0, 0, 1, 0,\r\n        0, 0, 0, 1)\r\n    );\r\n    \r\n    Matrix3f m2 = q10.toMatrix3x3;\r\n    assert(m2 == matrixf(\r\n        1, 0, 0,\r\n        0, 1, 0,\r\n        0, 0, 1)\r\n    );\r\n    \r\n    auto m3 = rotationMatrix!float(0, PI);\r\n    Quaternionf q11 = Quaternionf.fromMatrix(m3);\r\n    assert(q11.toMatrix4x4 == m3);\r\n    \r\n    Vector3f angles = Vector3f(PI * 0.5f, 0.0f, 0.0f);\r\n    Quaternionf q12 = Quaternionf.fromEulerAngles(angles);\r\n    assert(isAlmostZero(q12.toEulerAngles - angles));\r\n    assert(isAlmostZero(q12.rotationAxis - Vector3f(1, 0, 0)));\r\n    assert(isConsiderZero(q12.rotationAngle - angles.x));\r\n}\r\n\r\n/**\r\n * Setup a quaternion to rotate about world axis.\r\n * Theta must be in radians\r\n */\r\nQuaternion!(T) rotationQuaternion(T)(uint rotaxis, T theta)\r\n{\r\n    Quaternion!(T) res = Quaternion!(T).identity;\r\n    T thetaOver2 = theta * 0.5;\r\n\r\n    switch (rotaxis)\r\n    {\r\n        case Axis.x:\r\n            res.w = cos(thetaOver2);\r\n            res.x = sin(thetaOver2);\r\n            res.y = 0.0;\r\n            res.z = 0.0;\r\n            break;\r\n\r\n        case Axis.y:\r\n            res.w = cos(thetaOver2);\r\n            res.x = 0.0;\r\n            res.y = sin(thetaOver2);\r\n            res.z = 0.0;\r\n            break;\r\n\r\n        case Axis.z:\r\n            res.w = cos(thetaOver2);\r\n            res.x = 0.0;\r\n            res.y = 0.0;\r\n            res.z = sin(thetaOver2);\r\n            break;\r\n\r\n        default:\r\n        assert(0);\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/**\r\n * Setup a quaternion to rotate about specified axis.\r\n * Theta must be in radians\r\n */\r\nQuaternion!(T) rotationQuaternion(T)(Vector!(T,3) rotaxis, T theta)\r\n{\r\n    Quaternion!(T) res;\r\n\r\n    T thetaOver2 = theta * 0.5;\r\n    T sinThetaOver2 = sin(thetaOver2);\r\n\r\n    res.w = cos(thetaOver2);\r\n    res.x = rotaxis.x * sinThetaOver2;\r\n    res.y = rotaxis.y * sinThetaOver2;\r\n    res.z = rotaxis.z * sinThetaOver2;\r\n    return res;\r\n}\r\n\r\n/**\r\n * Setup a quaternion to represent rotation\r\n * between two unit-length vectors\r\n */\r\nQuaternion!(T) rotationBetween(T)(Vector!(T,3) a, Vector!(T,3) b)\r\n{\r\n    Quaternion!(T) q;\r\n\r\n    float d = dot(a, b);\r\n    float angle = acos(d);\r\n\r\n    Vector!(T,3) axis;\r\n    if (d < -0.9999)\r\n    {\r\n        Vector!(T,3) c;\r\n        if (a.y != 0.0 || a.z != 0.0)\r\n            c = Vector!(T,3)(1, 0, 0);\r\n        else\r\n            c = Vector!(T,3)(0, 1, 0);\r\n        axis = cross(a, c);\r\n        axis.normalize();\r\n        q = rotationQuaternion(axis, angle);\r\n    }\r\n    else if (d > 0.9999)\r\n    {\r\n        q = Quaternion!(T).identity;\r\n    }\r\n    else\r\n    {\r\n        axis = cross(a, b);\r\n        axis.normalize();\r\n        q = rotationQuaternion(axis, angle);\r\n    }\r\n\r\n    return q;\r\n}\r\n\r\n/**\r\n * Quaternion logarithm\r\n */\r\nQuaternion!(T) log(T)(Quaternion!(T) q)\r\n{\r\n    Quaternion!(T) res;\r\n    res.w = 0.0;\r\n\r\n    if (fabs(q.w) < 1.0)\r\n    {\r\n        T theta = acos(q.w);\r\n        T sin_theta = sin(theta);\r\n\r\n        if (fabs(sin_theta) > 0.00001)\r\n        {\r\n            T thetaOverSinTheta = theta / sin_theta;\r\n            res.x = q.x * thetaOverSinTheta;\r\n            res.y = q.y * thetaOverSinTheta;\r\n            res.z = q.z * thetaOverSinTheta;\r\n            return res;\r\n        }\r\n    }\r\n\r\n    res.x = q.x;\r\n    res.y = q.y;\r\n    res.z = q.z;\r\n    return res;\r\n}\r\n\r\n/**\r\n * Quaternion exponential\r\n */\r\nQuaternion!(T) exp(T) (Quaternion!(T) q)\r\n{\r\n    T theta = sqrt(dot(q, q));\r\n    T sin_theta = sin(theta);\r\n    Quaternion!(T) res;\r\n    res.w = cos(theta);\r\n\r\n    if (fabs(sin_theta) > 0.00001)\r\n    {\r\n        T sinThetaOverTheta = sin_theta / theta;\r\n        res.x = q.x * sinThetaOverTheta;\r\n        res.y = q.y * sinThetaOverTheta;\r\n        res.z = q.z * sinThetaOverTheta;\r\n    }\r\n    else\r\n    {\r\n        res.x = q.x;\r\n        res.y = q.y;\r\n        res.z = q.z;\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/**\r\n * Quaternion exponentiation\r\n */\r\nQuaternion!(T) pow(T) (Quaternion!(T) q, T exponent)\r\n{\r\n    if (fabs(q.w) > 0.9999)\r\n        return q;\r\n    T alpha = acos(q.w);\r\n    T newAlpha = alpha * exponent;\r\n    Vector!(T,3) n = Vector!(T,3)(q.x, q.y, q.z);\r\n    n *= sin(newAlpha) / sin(alpha);\r\n    return new Quaternion!(T)(n, cos(newAlpha));\r\n}\r\n\r\n/**\r\n * Spherical linear interpolation\r\n */\r\nQuaternion!(T) slerp(T)(\r\n    Quaternion!(T) q0,\r\n    Quaternion!(T) q1,\r\n    T t)\r\n{\r\n    if (t <= 0.0) return q0;\r\n    if (t >= 1.0) return q1;\r\n\r\n    T cosOmega = dot(q0, q1);\r\n    T q1w = q1.w;\r\n    T q1x = q1.x;\r\n    T q1y = q1.y;\r\n    T q1z = q1.z;\r\n\r\n    if (cosOmega < 0.0)\r\n    {\r\n        q1w = -q1w;\r\n        q1x = -q1x;\r\n        q1y = -q1y;\r\n        q1z = -q1z;\r\n        cosOmega = -cosOmega;\r\n    }\r\n    assert (cosOmega < 1.1);\r\n\r\n    T k0, k1;\r\n    if (cosOmega > 0.9999)\r\n    {\r\n        k0 = 1.0 - t;\r\n        k1 = t;\r\n    }\r\n    else\r\n    {\r\n        T sinOmega = sqrt(1.0 - (cosOmega * cosOmega));\r\n        T omega = atan2(sinOmega, cosOmega);\r\n        T oneOverSinOmega = 1.0 / sinOmega;\r\n        k0 = sin((1.0 - t) * omega) * oneOverSinOmega;\r\n        k1 = sin(t * omega) * oneOverSinOmega;\r\n    }\r\n\r\n    Quaternion!(T) res = Quaternion!(T)\r\n    (\r\n        (k0 * q0.x) + (k1 * q1x),\r\n        (k0 * q0.y) + (k1 * q1y),\r\n        (k0 * q0.z) + (k1 * q1z),\r\n        (k0 * q0.w) + (k1 * q1w)\r\n    );\r\n    return res;\r\n}\r\n\r\n/**\r\n * Spherical cubic interpolation\r\n */\r\nQuaternion!(T) squad(T)(\r\n    Quaternion!(T) q0,\r\n    Quaternion!(T) qa,\r\n    Quaternion!(T) qb,\r\n    Quaternion!(T) q1,\r\n    T t)\r\n{\r\n    T slerp_t = 2.0 * t * (1.0 - t);\r\n    Quaternion!(T) slerp_q0 = slerp(q0, q1, t);\r\n    Quaternion!(T) slerp_q1 = slerp(qa, qb, t);\r\n    return slerp(slerp_q0, slerp_q1, slerp_t);\r\n}\r\n\r\n/**\r\n * Compute intermediate quaternions for building spline segments\r\n */\r\nQuaternion!(T) intermediate(T)(\r\n    Quaternion!(T) qprev,\r\n    Quaternion!(T) qcurr,\r\n    Quaternion!(T) qnext,\r\nref Quaternion!(T) qa,\r\nref Quaternion!(T) qb)\r\nin\r\n{\r\n    assert (dot(qprev, qprev) <= 1.0001);\r\n    assert (dot(qcurr, qcurr) <= 1.0001);\r\n}\r\ndo\r\n{\r\n    Quaternion!(T) inv_prev = qprev.conj;\r\n    Quaternion!(T) inv_curr = qcurr.conj;\r\n\r\n    Quaternion!(T) p0 = inv_prev * qcurr;\r\n    Quaternion!(T) p1 = inv_curr * qnext;\r\n\r\n    Quaternion!(T) arg = (log(p0) - log(p1)) * 0.25;\r\n\r\n    qa = qcurr * exp( arg);\r\n    qb = qcurr * exp(-arg);\r\n}\r\n"
  },
  {
    "path": "dlib/math/sse.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov, Alexander Perfilyev\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * SSE-based optimizations for common vector and matrix operations\r\n *\r\n * Description:\r\n * This module implements some frequently used vector and matrix operations using SSE instructions.\r\n *\r\n * Copyright: Timur Gafarov, Alexander Perfilyev 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov, Alexander Perfilyev\r\n */\r\nmodule dlib.math.sse;\r\n\r\nimport dlib.math.vector;\r\nimport dlib.math.matrix;\r\n\r\nversion(GNU)\r\n{\r\n    pragma(inline, true);\r\n   \r\n    version(X86_Any)\r\n    {\r\n        /// Vector addition\r\n        Vector4f sseAdd4(Vector4f a, Vector4f b)\r\n        {\r\n            asm {\r\n                \"movups %[a], %%xmm0 \\n\" ~  // Load vector a into xmm0\r\n                \"movups %[b], %%xmm1 \\n\" ~  // Load vector b into xmm1\r\n                \"addps %%xmm1, %%xmm0 \\n\" ~ // Add xmm1 to xmm0\r\n                \"movups %%xmm0, %[a] \\n\"    // Store the result back in vector a\r\n                : [a] \"+m\" (a)              // Output operand a, constrained to memory\r\n                : [b] \"m\" (b)               // Input operand b, constrained to memory\r\n                : \"%xmm0\", \"%xmm1\";         // Clobbered registers\r\n            }\r\n            \r\n            return a;\r\n        }\r\n    \r\n        /// Vector subtraction for GNU D Compiler (using AVX)\r\n        Vector4f sseSub4(Vector4f a, Vector4f b)\r\n        {\r\n            asm\r\n            {\r\n                \"movups %[a], %%xmm0 \\n\" ~  // Load vector a into xmm0\r\n                \"movups %[b], %%xmm1 \\n\" ~  // Load vector b into xmm1\r\n                \"subps %%xmm1, %%xmm0 \\n\" ~ // Subtract xmm1 from xmm0\r\n                \"movups %%xmm0, %[a] \\n\"    // Store the result back in vector a\r\n                : [a] \"+m\" (a)              // Output operand a, constrained to memory\r\n                : [b] \"m\" (b)               // Input operand b, constrained to memory\r\n                : \"%xmm0\", \"%xmm1\";         // Clobbered registers\r\n            }\r\n            \r\n            return a;\r\n        }\r\n        \r\n        /// Vector multiplication for GNU D Compiler (using AVX)\r\n        Vector4f sseMul4(Vector4f a, Vector4f b)\r\n        {\r\n            asm\r\n            {\r\n                \"movups %[a], %%xmm0 \\n\" ~  // Load vector a into xmm0\r\n                \"movups %[b], %%xmm1 \\n\" ~  // Load vector b into xmm1\r\n                \"mulps %%xmm1, %%xmm0 \\n\" ~ // Multiply xmm0 by xmm1\r\n                \"movups %%xmm0, %[a] \\n\"    // Store the result back in vector a\r\n                : [a] \"+m\" (a)              // Output operand a, constrained to memory\r\n                : [b] \"m\" (b)               // Input operand b, constrained to memory\r\n                : \"%xmm0\", \"%xmm1\";         // Clobbered registers\r\n            }\r\n            \r\n            return a;\r\n        }\r\n        \r\n        /// Vector division for GNU D Compiler (using AVX)\r\n        Vector4f sseDiv4(Vector4f a, Vector4f b)\r\n        {\r\n            asm\r\n            {\r\n                \"movups %[a], %%xmm0 \\n\" ~  // Load vector a into xmm0\r\n                \"movups %[b], %%xmm1 \\n\" ~  // Load vector b into xmm1\r\n                \"divps %%xmm1, %%xmm0 \\n\" ~ // Divide xmm0 by xmm1\r\n                \"movups %%xmm0, %[a] \\n\"    // Store the result back in vector a\r\n                : [a] \"+m\" (a)              // Output operand a, constrained to memory\r\n                : [b] \"m\" (b)               // Input operand b, constrained to memory\r\n                : \"%xmm0\", \"%xmm1\";         // Clobbered registers\r\n            }\r\n            \r\n            return a;\r\n        }\r\n        \r\n        /// Vector dot product for GNU D Compiler (using SSE)\r\n        float sseDot4(Vector4f a, Vector4f b)\r\n        {\r\n            asm\r\n            {\r\n                \"movups %[a], %%xmm0 \\n\" ~   // Load vector a into xmm0\r\n                \"movups %[b], %%xmm1 \\n\" ~   // Load vector b into xmm1\r\n                \"mulps %%xmm1, %%xmm0 \\n\" ~  // Multiply xmm0 by xmm1\r\n                \r\n                // Horizontal addition\r\n                \"movhlps %%xmm0, %%xmm1 \\n\" ~// Copy the high 64 bits to the low 64 bits of xmm1\r\n                \"addps %%xmm1, %%xmm0 \\n\" ~  // Add xmm1 to xmm0\r\n                \r\n                \"movups %%xmm0, %[a] \\n\"     // Store the result back in vector a\r\n                : [a] \"+m\" (a)               // Output operand a, constrained to memory\r\n                : [b] \"m\" (b)                // Input operand b, constrained to memory\r\n                : \"%xmm0\", \"%xmm1\";          // Clobbered registers\r\n            }\r\n            \r\n            return a[0];\r\n        }\r\n        \r\n        /// Vector cross product for GNU D Compiler (using SSE)\r\n        Vector4f sseCross3(Vector4f a, Vector4f b)\r\n        {\r\n            asm\r\n            {\r\n                \"movups %[a], %%xmm0 \\n\" ~            // Load vector a into xmm0\r\n                \"movups %[b], %%xmm1 \\n\" ~            // Load vector b into xmm1\r\n                \"movaps %%xmm0, %%xmm2 \\n\" ~          // Copy xmm0 to xmm2\r\n                \"movaps %%xmm1, %%xmm3 \\n\" ~          // Copy xmm1 to xmm3\r\n                \r\n                \"shufps $0xC9, %%xmm0, %%xmm0 \\n\" ~   // Shuffle xmm0 according to 0xC9\r\n                \"shufps $0xD2, %%xmm1, %%xmm1 \\n\" ~   // Shuffle xmm1 according to 0xD2\r\n                \"shufps $0xD2, %%xmm2, %%xmm2 \\n\" ~   // Shuffle xmm2 according to 0xD2\r\n                \"shufps $0xC9, %%xmm3, %%xmm3 \\n\" ~   // Shuffle xmm3 according to 0xC9\r\n                \r\n                \"mulps %%xmm1, %%xmm0 \\n\" ~           // Multiply xmm0 by xmm1\r\n                \"mulps %%xmm3, %%xmm2 \\n\" ~           // Multiply xmm2 by xmm3\r\n                \r\n                \"subps %%xmm2, %%xmm0 \\n\" ~           // Subtract xmm2 from xmm0\r\n                \r\n                \"movups %%xmm0, %[a] \\n\"              // Store the result back in vector a\r\n                : [a] \"+m\" (a)                        // Output operand a, constrained to memory\r\n                : [b] \"m\" (b)                         // Input operand b, constrained to memory\r\n                : \"%xmm0\", \"%xmm1\", \"%xmm2\", \"%xmm3\"; // Clobbered registers\r\n            }\r\n            \r\n            return a;\r\n        }\r\n        \r\n        /// Matrix multiplication for GNU D Compiler (using SSE)\r\n        Matrix4x4f sseMulMat4(Matrix4x4f a, Matrix4x4f b)\r\n        {\r\n            Matrix4x4f r;\r\n            Vector4f a_line, b_line, r_line;\r\n            float _b;\r\n            uint i, j;\r\n            Vector4f* _rp;\r\n        \r\n            for (i = 0; i < 16; i += 4)\r\n            {\r\n                a_line = *cast(Vector4f*)(a.arrayof.ptr);\r\n                _b = *(b.arrayof.ptr + i);\r\n                \r\n                asm\r\n                {\r\n                    \"movups %[a_line], %%xmm0 \\n\" ~        // Load vector a_line into xmm0\r\n                    \r\n                    \"mov %[_b], %%eax \\n\" ~                // Move _b into the EAX register\r\n                    \"movd %%eax, %%xmm1 \\n\" ~              // Move EAX into xmm1\r\n                    \r\n                    \"shufps $0, %%xmm1, %%xmm1 \\n\" ~       // Shuffle xmm1 according to 0\r\n                    \r\n                    \"mulps %%xmm1, %%xmm0 \\n\" ~            // Multiply xmm0 by xmm1\r\n                    \"movups %%xmm0, %[r_line]\"             // Store the result in r_line\r\n                    \r\n                    : [r_line] \"=m\" (r_line)               // Output operand r_line, constrained to memory\r\n                    : [a_line] \"m\" (a_line), [_b] \"r\" (_b) // Input operands a_line and _b, constrained to memory and register\r\n                    : \"%xmm0\", \"%xmm1\", \"%eax\";            // Clobbered registers\r\n                }\r\n        \r\n                for (j = 1; j < 4; j++)\r\n                {\r\n                    a_line = *cast(Vector4f*)(a.arrayof.ptr + j * 4);\r\n                    _b = *(b.arrayof.ptr + i + j);\r\n                    \r\n                    asm\r\n                    {\r\n                        \"movups %[a_line], %%xmm0 \\n\" ~           // Load vector a_line into xmm0\r\n                        \r\n                        \"mov %[_b], %%eax \\n\" ~                   // Move _b into the EAX register\r\n                        \"movd %%eax, %%xmm1 \\n\" ~                 // Move EAX into xmm1\r\n                        \"shufps $0, %%xmm1, %%xmm1 \\n\" ~          // Shuffle xmm1 according to 0\r\n                        \r\n                        \"mulps %%xmm1, %%xmm0 \\n\" ~               // Multiply xmm0 by xmm1\r\n                        \r\n                        \"movups %[r_line], %%xmm2 \\n\" ~           // Load r_line into xmm2\r\n                        \"addps %%xmm2, %%xmm0 \\n\" ~               // Add xmm2 to xmm0\r\n                        \r\n                        \"movups %%xmm0, %[r_line]\"                // Store the result back in r_line\r\n                        : [r_line] \"=m\" (r_line)                  // Output and input operands\r\n                        : [a_line] \"m\" (a_line), [_b] \"r\" (_b)    // Input operand b, constrained to memory\r\n                        : \"%xmm0\", \"%xmm1\", \"%xmm2\", \"%eax\";      // Clobbered registers\r\n                    }\r\n                }\r\n                \r\n                _rp = cast(Vector4f*)(r.arrayof.ptr + i);\r\n                \r\n                version(X86) asm\r\n                {\r\n                    \"mov %[_rp], %%eax \\n\" ~          // Move _rp into the EAX register\r\n                    \"movups %%xmm0,(%%eax)\"           // Move xmm0 to the memory location pointed by EAX\r\n                    : [_rp] \"+r\" (_rp)                // Output and input operands\r\n                    :                                 // No additional input operands\r\n                    : \"%eax\", \"%xmm0\";                // Clobbered registers\r\n                }\r\n                version(X86_64) asm\r\n                {\r\n                    \"mov %[_rp], %%rax \\n\" ~           // Move _rp into the RAX register\r\n                    \"movups %%xmm0, (%%rax)\"           // Move xmm0 to the memory location pointed by RAX\r\n                    : [_rp] \"+r\" (_rp)                 // Output and input operands\r\n                    :                                  // No additional input operands\r\n                    : \"%rax\", \"%xmm0\";                 // Clobbered registers\r\n                }\r\n            }\r\n            \r\n            return r;\r\n        }\r\n    }\r\n}\r\n\r\nversion(DMD)\r\n{\r\n    pragma(inline, true):\r\n    \r\n    version(X86_Any)\r\n    {\r\n        /// Vector addition\r\n        Vector4f sseAdd4(Vector4f a, Vector4f b)\r\n        {\r\n            asm\r\n            {\r\n                movups XMM0, a;\r\n                movups XMM1, b;\r\n                addps XMM0, XMM1;\r\n                movups a, XMM0;\r\n            }\r\n            \r\n            return a;\r\n        }\r\n    \r\n        /// Vector subtraction\r\n        Vector4f sseSub4(Vector4f a, Vector4f b)\r\n        {\r\n            asm\r\n            {\r\n                movups XMM0, a;\r\n                movups XMM1, b;\r\n                subps XMM0, XMM1;\r\n                movups a, XMM0;\r\n            }\r\n            \r\n            return a;\r\n        }\r\n    \r\n        /// Vector multiplication\r\n        Vector4f sseMul4(Vector4f a, Vector4f b)\r\n        {\r\n            asm\r\n            {\r\n                movups XMM0, a;\r\n                movups XMM1, b;\r\n                mulps XMM0, XMM1;\r\n                movups a, XMM0;\r\n            }\r\n            \r\n            return a;\r\n        }\r\n    \r\n        /// Vector division\r\n        Vector4f sseDiv4(Vector4f a, Vector4f b)\r\n        {\r\n            asm\r\n            {\r\n                movups XMM0, a;\r\n                movups XMM1, b;\r\n                divps XMM0, XMM1;\r\n                movups a, XMM0;\r\n            }\r\n            \r\n            return a;\r\n        }\r\n    \r\n        /// Vector dot product\r\n        float sseDot4(Vector4f a, Vector4f b)\r\n        {\r\n            asm\r\n            {\r\n                movups XMM0, a;\r\n                movups XMM1, b;\r\n                mulps XMM0, XMM1;\r\n                \r\n                // Horizontal addition\r\n                movhlps XMM1, XMM0;\r\n                addps XMM0, XMM1;\r\n                movups XMM1, XMM0;\r\n                shufps XMM1, XMM1, 0x55;\r\n                addps XMM0, XMM1;\r\n                \r\n                movups a, XMM0;\r\n            }\r\n            \r\n            return a[0];\r\n        }\r\n        \r\n        /// Vector cross product\r\n        Vector4f sseCross3(Vector4f a, Vector4f b)\r\n        {\r\n            asm\r\n            {\r\n                movups XMM0, a;\r\n                movups XMM1, b;\r\n                movaps XMM2, XMM0;\r\n                movaps XMM3, XMM1;\r\n                \r\n                shufps XMM0, XMM0, 0xC9;\r\n                shufps XMM1, XMM1, 0xD2;\r\n                shufps XMM2, XMM2, 0xD2;\r\n                shufps XMM3, XMM3, 0xC9;\r\n                \r\n                mulps XMM0, XMM1;\r\n                mulps XMM2, XMM3;\r\n                \r\n                subps XMM0, XMM2;\r\n                \r\n                movups a, XMM0;\r\n            }\r\n            \r\n            return a;\r\n        }\r\n    \r\n        /// Matrix multiplication\r\n        Matrix4x4f sseMulMat4(Matrix4x4f a, Matrix4x4f b)\r\n        {\r\n            Matrix4x4f r;\r\n            Vector4f a_line, b_line, r_line;\r\n            float _b;\r\n            uint i, j;\r\n            Vector4f* _rp;\r\n            for (i = 0; i < 16; i += 4)\r\n            {\r\n                a_line = *cast(Vector4f*)(a.arrayof.ptr);\r\n                _b = *(b.arrayof.ptr + i);\r\n                asm\r\n                {\r\n                    movups XMM0, a_line;\r\n                    \r\n                    mov EAX, _b;\r\n                    movd XMM1, EAX;\r\n                    \r\n                    shufps XMM1, XMM1, 0;\r\n                    \r\n                    mulps XMM0, XMM1;\r\n                    movups r_line, XMM0;\r\n                }\r\n                \r\n                for (j = 1; j < 4; j++)\r\n                {\r\n                    a_line = *cast(Vector4f*)(a.arrayof.ptr + j * 4);\r\n                    _b = *(b.arrayof.ptr + i + j);\r\n                    asm\r\n                    {\r\n                        movups XMM0, a_line;\r\n                        \r\n                        mov EAX, _b;\r\n                        movd XMM1, EAX;\r\n                        shufps XMM1, XMM1, 0;\r\n                        \r\n                        mulps XMM0, XMM1;\r\n                        \r\n                        movups XMM2, r_line;\r\n                        addps XMM0, XMM2;\r\n                        \r\n                        movups r_line, XMM0;\r\n                    }\r\n                }\r\n                \r\n                _rp = cast(Vector4f*)(r.arrayof.ptr + i);\r\n                version(X86) asm\r\n                {\r\n                    mov EAX, _rp;\r\n                    movups [EAX], XMM0;\r\n                }\r\n                version(X86_64) asm\r\n                {\r\n                    mov RAX, _rp;\r\n                    movups [RAX], XMM0;\r\n                }\r\n            }\r\n            \r\n            return r;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/math/tensor.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * N-dimensional numeric data structure\r\n *\r\n * Copyright: Timur Gafarov 2016-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.math.tensor;\r\n\r\nimport std.traits;\r\nimport std.math;\r\nimport std.conv;\r\nimport std.range;\r\nimport std.format;\r\n\r\nimport dlib.core.tuple;\r\nimport dlib.core.compound;\r\nimport dlib.core.memory;\r\n\r\nT zero(T)() if (isNumeric!T)\r\n{\r\n    return T(0);\r\n}\r\n\r\nsize_t calcLen(T...)(T n)\r\n{\r\n    size_t len = 1;\r\n    foreach(s; n)\r\n        len *= s;\r\n    return len;\r\n}\r\n\r\ntemplate NTypeTuple(T, int n)\r\n{\r\n    static if (n <= 0)\r\n        alias NTypeTuple = Tuple!();\r\n    else\r\n        alias NTypeTuple = Tuple!(NTypeTuple!(T, n-1), T);\r\n}\r\n\r\nenum MaxStaticTensorSize = double.sizeof * 16; // fit 4x4 matrix of doubles\r\n\r\n/**\r\n  Generic multi-dimensional array template\r\n  \r\n  Description:\r\n  This template mainly serves as a base for creating various\r\n  more specialized algebraic objects via encapsulation.\r\n  Think of Tensor as a backend for e.g. Vector and Matrix.\r\n\r\n  T - element type, usually numeric (float or double)\r\n  \r\n  dim - number of dimensions (tensor order):\r\n    0 - scalar,\r\n    1 - vector,\r\n    2 - matrix,\r\n    3 - 3D array,\r\n    (higer dimensions are also possible).\r\n  \r\n  sizes - tuple defining sizes for each dimension:\r\n    3 - 3-vector,\r\n    4,4 - 4x4 matrix,\r\n    etc.\r\n\r\n  Data storage type (stack or heap) is statically selected: if given size(s)\r\n  imply data size larger than MaxStaticTensorSize, data is allocated\r\n  on heap (as dynamic array). Otherwise, data is allocated on stack (as static array).\r\n */\r\ntemplate Tensor(T, size_t dim, sizes...)\r\n{\r\n    // TODO:\r\n    // - External storage\r\n    // - Component-wise addition, subtraction\r\n\r\n    struct Tensor\r\n    {\r\n        private enum size_t _dataLen = calcLen(sizes);\r\n\r\n        alias ElementType = T;\r\n        enum size_t dimensions = dim;\r\n        enum size_t order = dim;\r\n        alias Sizes = sizes;\r\n        enum bool isTensor = true;\r\n        enum bool isScalar = (order == 0 && _dataLen == 1);\r\n        enum bool isVector = (order == 1);\r\n        enum bool isMatrix = (order == 2);\r\n        enum bool dynamic = (_dataLen * T.sizeof) > MaxStaticTensorSize;\r\n\r\n        static assert(order == sizes.length,\r\n                \"Illegal size for Tensor\");\r\n\r\n        static if (order > 0)\r\n        {\r\n            static assert(sizes.length,\r\n                \"Illegal size for 0-order Tensor\");\r\n        }\r\n\r\n        static if (isVector)\r\n        {\r\n            static assert(sizes.length == 1,\r\n                \"Illegal size for 1st-order Tensor\");\r\n        }\r\n\r\n        static if (isMatrix)\r\n        {\r\n            static assert(sizes.length == 2,\r\n                \"Illegal size for 2nd-order Tensor\");\r\n\r\n            enum size_t rows = sizes[0];\r\n            enum size_t cols = sizes[1];\r\n\r\n            enum bool isSquareMatrix = (rows == cols);\r\n\r\n            static if (isSquareMatrix)\r\n            {\r\n                enum size = sizes[0];\r\n            }\r\n        }\r\n        else\r\n        {\r\n            enum bool isSquareMatrix = false;\r\n\r\n            static if (sizes.length > 0)\r\n            {\r\n                enum size = sizes[0];\r\n            }\r\n        }\r\n\r\n       /**\r\n        * Single element constructor\r\n        */\r\n        this(T initVal)\r\n        {\r\n            static if (dynamic)\r\n            {\r\n                allocate();\r\n            }\r\n\r\n            foreach(ref v; data)\r\n                v = initVal;\r\n        }\r\n\r\n       /**\r\n        * Tensor constructor\r\n        */\r\n        this(Tensor!(T, order, sizes) t)\r\n        {\r\n            static if (dynamic)\r\n            {\r\n                allocate();\r\n            }\r\n\r\n            foreach(i, v; t.arrayof)\r\n            {\r\n                arrayof[i] = v;\r\n            }\r\n        }\r\n\r\n       /**\r\n        * Tuple constructor\r\n        */\r\n        this(F...)(F components) if (F.length > 1)\r\n        {\r\n            static if (dynamic)\r\n            {\r\n                allocate();\r\n            }\r\n\r\n            foreach(i, v; components)\r\n            {\r\n                static if (i < arrayof.length)\r\n                    arrayof[i] = cast(T)v;\r\n            }\r\n        }\r\n\r\n        static Tensor!(T, order, sizes) init()\r\n        {\r\n            Tensor!(T, order, sizes) res;\r\n            static if (dynamic)\r\n            {\r\n                res.allocate();\r\n            }\r\n            return res;\r\n        }\r\n\r\n        static Tensor!(T, order, sizes) zero()\r\n        {\r\n            Tensor!(T, order, sizes) res;\r\n            static if (dynamic)\r\n            {\r\n                res.allocate();\r\n            }\r\n            foreach(ref v; res.data)\r\n                v = .zero!T();\r\n            return res;\r\n        }\r\n\r\n       /**\r\n        * T = Tensor[index]\r\n        */\r\n        auto ref T opIndex(this X)(size_t index)\r\n        in\r\n        {\r\n            assert ((0 <= index) && (index < _dataLen),\r\n                \"Tensor.opIndex: array index out of bounds\");\r\n        }\r\n        do\r\n        {\r\n            return arrayof[index];\r\n        }\r\n\r\n       /**\r\n        * Tensor[index] = T\r\n        */\r\n        void opIndexAssign(T n, size_t index)\r\n        in\r\n        {\r\n            assert (index < _dataLen,\r\n                \"Tensor.opIndexAssign: array index out of bounds\");\r\n        }\r\n        do\r\n        {\r\n            arrayof[index] = n;\r\n        }\r\n\r\n       /**\r\n        * T = Tensor[i, j, ...]\r\n        */\r\n        T opIndex(I...)(in I indices) const if (I.length == sizes.length)\r\n        {\r\n            size_t index = 0;\r\n            size_t m = 1;\r\n            foreach(i, ind; indices)\r\n            {\r\n                index += ind * m;\r\n                m *= sizes[i];\r\n            }\r\n            return arrayof[index];\r\n        }\r\n\r\n       /**\r\n        * Tensor[i, j, ...] = T\r\n        */\r\n        T opIndexAssign(I...)(in T t, in I indices) if (I.length == sizes.length)\r\n        {\r\n            size_t index = 0;\r\n            size_t m = 1;\r\n            foreach(i, ind; indices)\r\n            {\r\n                index += ind * m;\r\n                m *= sizes[i];\r\n            }\r\n            return (arrayof[index] = t);\r\n        }\r\n\r\n       /**\r\n        * Tensor = Tensor\r\n        */\r\n        void opAssign (Tensor!(T, order, sizes) t)\r\n        {\r\n            static if (dynamic)\r\n            {\r\n                allocate();\r\n            }\r\n\r\n            foreach(i, v; t.arrayof)\r\n            {\r\n                arrayof[i] = v;\r\n            }\r\n        }\r\n\r\n        alias Indices = NTypeTuple!(size_t, order);\r\n\r\n        int opApply(scope int delegate(ref T v, Indices indices) dg)\r\n        {\r\n            int result = 0;\r\n            Compound!(Indices) ind;\r\n            size_t index = 0;\r\n\r\n            while(index < data.length)\r\n            {\r\n                result = dg(data[index], ind.tuple);\r\n                if (result)\r\n                    break;\r\n\r\n                ind[0]++;\r\n\r\n                foreach(i; RangeTuple!(0, order))\r\n                {\r\n                    if (ind[i] == sizes[i])\r\n                    {\r\n                        ind[i] = 0;\r\n                        static if (i < order-1)\r\n                        {\r\n                            ind[i+1]++;\r\n                        }\r\n                    }\r\n                }\r\n\r\n                index++;\r\n            }\r\n\r\n            return result;\r\n        }\r\n\r\n        @property string toString() const\r\n        {\r\n            static if (isScalar)\r\n            {\r\n                return x.to!string;\r\n            }\r\n            else\r\n            {\r\n                auto writer = appender!string();\r\n                formattedWrite(writer, \"%s\", arrayof);\r\n                return writer.data;\r\n            }\r\n        }\r\n\r\n        @property size_t length()\r\n        {\r\n            return data.length;\r\n        }\r\n\r\n        @property bool initialized()\r\n        {\r\n            return (data.length > 0);\r\n        }\r\n\r\n        static if (isVector)\r\n        {\r\n            private static bool valid(string s)\r\n            {\r\n                if (s.length < 2)\r\n                    return false;\r\n\r\n                foreach(c; s)\r\n                {\r\n                    switch(c)\r\n                    {\r\n                        case 'w', 'a', 'q':\r\n                            if (size < 4) return false;\r\n                            else break;\r\n                        case 'z', 'b', 'p':\r\n                            if (size < 3) return false;\r\n                            else break;\r\n                        case 'y', 'g', 't':\r\n                            if (size < 2) return false;\r\n                            else break;\r\n                        case 'x', 'r', 's':\r\n                            if (size < 1) return false;\r\n                            else break;\r\n                        default:\r\n                            return false;\r\n                    }\r\n                }\r\n                return true;\r\n            }\r\n\r\n            static if (size < 5)\r\n            {\r\n               /**\r\n                * Symbolic element access for vector\r\n                */\r\n                private static string vecElements(string[4] letters) @property\r\n                {\r\n                    string res;\r\n                    foreach (i; 0..size)\r\n                    {\r\n                        res ~= \"T \" ~ letters[i] ~ \"; \";\r\n                    }\r\n                    return res;\r\n                }\r\n            }\r\n\r\n           /**\r\n            * Swizzling\r\n            */\r\n            template opDispatch(string s) if (valid(s))\r\n            {\r\n                static if (s.length <= 4)\r\n                {\r\n                    @property auto ref opDispatch(this X)()\r\n                    {\r\n                        auto extend(string s)\r\n                        {\r\n                            while (s.length < 4)\r\n                                s ~= s[$-1];\r\n                            return s;\r\n                        }\r\n\r\n                        enum p = extend(s);\r\n                        enum i = (char c) => ['x':0, 'y':1, 'z':2, 'w':3,\r\n                                              'r':0, 'g':1, 'b':2, 'a':3,\r\n                                              's':0, 't':1, 'p':2, 'q':3][c];\r\n                        enum i0 = i(p[0]),\r\n                             i1 = i(p[1]),\r\n                             i2 = i(p[2]),\r\n                             i3 = i(p[3]);\r\n\r\n                        static if (s.length == 4)\r\n                            return Tensor!(T,1,4)(arrayof[i0], arrayof[i1], arrayof[i2], arrayof[i3]);\r\n                        else static if (s.length == 3)\r\n                            return Tensor!(T,1,3)(arrayof[i0], arrayof[i1], arrayof[i2]);\r\n                        else static if (s.length == 2)\r\n                            return Tensor!(T,1,2)(arrayof[i0], arrayof[i1]);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        static if (dynamic)\r\n        {\r\n            T[] data;\r\n\r\n            private void allocate()\r\n            {\r\n                if (data.length == 0)\r\n                    data = New!(T[])(_dataLen);\r\n            }\r\n\r\n            void free()\r\n            {\r\n                if (data.length)\r\n                    Delete(data);\r\n            }\r\n        }\r\n        else\r\n        {\r\n            union\r\n            {\r\n                T[_dataLen] data;\r\n\r\n                static if (isScalar)\r\n                {\r\n                    T x;\r\n                }\r\n\r\n                static if (isVector)\r\n                {\r\n                    static if (size < 5)\r\n                    {\r\n                        struct { mixin(vecElements([\"x\", \"y\", \"z\", \"w\"])); }\r\n                        struct { mixin(vecElements([\"r\", \"g\", \"b\", \"a\"])); }\r\n                        struct { mixin(vecElements([\"s\", \"t\", \"p\", \"q\"])); }\r\n                    }\r\n                }\r\n            }\r\n\r\n            static if (isScalar)\r\n            {\r\n                alias x this;\r\n            }\r\n        }\r\n\r\n        alias arrayof = data;\r\n\r\n    }\r\n}\r\n\r\n/**\r\n * Tensor product\r\n *\r\n * Description:\r\n * Tensor product of two tensors of order N\r\n * and sizes S1 and S2 gives a tensor of order 2N\r\n * and sizes (S1,S2).\r\n */\r\nauto tensorProduct(T1, T2)(T1 t1, T2 t2)\r\n{\r\n    // TODO: ensure T1, t2 are Tensors\r\n    // TODO: if T1 and T2 are scalars, use ordinary multiplication\r\n    // TODO: if T1 and T2 are vectors, use optimized version\r\n \r\n    static assert(T1.dimensions == T2.dimensions);\r\n\r\n    alias T = T1.ElementType;\r\n    enum order = T1.dimensions + T2.dimensions;\r\n    alias sizes = Tuple!(T2.Sizes, T1.Sizes);\r\n    alias TensorType = Tensor!(T, order, sizes);\r\n\r\n    TensorType t;\r\n    static if (TensorType.dynamic)\r\n    {\r\n        t = TensorType.init();\r\n    }\r\n\r\n    Compound!(TensorType.Indices) ind;\r\n    size_t index = 0;\r\n\r\n    while(index < t.data.length)\r\n    {\r\n        t.data[index] =\r\n            t2[ind.tuple[0..$/2]] *\r\n            t1[ind.tuple[$/2..$]];\r\n\r\n        ind[0]++;\r\n\r\n        foreach(i; RangeTuple!(0, order))\r\n        {\r\n            if (ind[i] == sizes[i])\r\n            {\r\n                ind[i] = 0;\r\n                static if (i < order-1)\r\n                {\r\n                    ind[i+1]++;\r\n                }\r\n            }\r\n        }\r\n\r\n        index++;\r\n    }\r\n\r\n    return t;\r\n}\r\n"
  },
  {
    "path": "dlib/math/transformation.d",
    "content": "/*\r\nCopyright (c) 2013-2025 Timur Gafarov, Martin Cejp\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Matrix-based geometric transformations\r\n * \r\n * Copyright: Timur Gafarov 2013-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.math.transformation;\r\n\r\nimport std.math;\r\n\r\nimport dlib.math.utils;\r\nimport dlib.math.vector;\r\nimport dlib.math.matrix;\r\nimport dlib.math.quaternion;\r\n\r\n/**\r\n * Setup a rotation matrix, given Euler angles in radians\r\n */\r\nMatrix!(T,4) fromEuler(T) (Vector!(T,3) v)\r\n{\r\n    auto res = Matrix!(T,4).identity;\r\n\r\n    T cx = cos(v.x);\r\n    T sx = sin(v.x);\r\n    T cy = cos(v.y);\r\n    T sy = sin(v.y);\r\n    T cz = cos(v.z);\r\n    T sz = sin(v.z);\r\n\r\n    T sxsy = sx * sy;\r\n    T cxsy = cx * sy;\r\n\r\n    res.a11 =  (cy * cz);\r\n    res.a12 =  (sxsy * cz) + (cx * sz);\r\n    res.a13 = -(cxsy * cz) + (sx * sz);\r\n\r\n    res.a21 = -(cy * sz);\r\n    res.a22 = -(sxsy * sz) + (cx * cz);\r\n    res.a23 =  (cxsy * sz) + (sx * cz);\r\n\r\n    res.a31 =  (sy);\r\n    res.a32 = -(sx * cy);\r\n    res.a33 =  (cx * cy);\r\n\r\n    return res;\r\n}\r\n\r\n/**\r\n * Setup the Euler angles in radians, given a rotation matrix\r\n */\r\nVector!(T,3) toEuler(T) (Matrix!(T,4) m)\r\ndo\r\n{\r\n    Vector!(T,3) v;\r\n\r\n    v.y = asin(m.a31);\r\n\r\n    T cy = cos(v.y);\r\n    T oneOverCosY = 1.0 / cy;\r\n\r\n    if (fabs(cy) > 0.001)\r\n    {\r\n        v.x = atan2(-m.a32 * oneOverCosY, m.a33 * oneOverCosY);\r\n        v.z = atan2(-m.a21 * oneOverCosY, m.a11 * oneOverCosY);\r\n    }\r\n    else\r\n    {\r\n        v.x = 0.0;\r\n        v.z = atan2(m.a12, m.a22);\r\n    }\r\n\r\n    return v;\r\n}\r\n\r\n/**\r\n * Right vector of the matrix\r\n */\r\nVector!(T,3) right(T) (Matrix!(T,4) m)\r\ndo\r\n{\r\n    return Vector!(T,3)(m.a11, m.a21, m.a31);\r\n}\r\n\r\n/**\r\n * Up vector of the matrix\r\n */\r\nVector!(T,3) up(T) (Matrix!(T,4) m)\r\ndo\r\n{\r\n    return Vector!(T,3)(m.a12, m.a22, m.a32);\r\n}\r\n\r\n/**\r\n * Forward vector of the matrix\r\n */\r\nVector!(T,3) forward(T) (Matrix!(T,4) m)\r\ndo\r\n{\r\n    return Vector!(T,3)(m.a13, m.a23, m.a33);\r\n}\r\n\r\n/**\r\n * Translation vector of the matrix\r\n */\r\nVector!(T,3) translation(T) (Matrix!(T,4) m)\r\ndo\r\n{\r\n    return Vector!(T,3)(m.a14, m.a24, m.a34);\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Matrix4f tm = matrixf(\r\n        1.0f, 0.0f, 0.0f, 3.0f,\r\n        0.0f, 1.0f, 0.0f, 5.0f,\r\n        0.0f, 0.0f, 1.0f, 2.5f,\r\n        0.0f, 0.0f, 0.0f, 1.0f,\r\n    );\r\n    \r\n    Vector3f t = translation(tm);\r\n    \r\n    assert(t == Vector3f(3.0f, 5.0f, 2.5f));\r\n}\r\n\r\n/**\r\n * Scaling vector of the matrix\r\n */\r\nVector!(T,3) scaling(T) (Matrix!(T,4) m)\r\ndo\r\n{\r\n    T sx = Vector!(T,3)(m.a11, m.a12, m.a13).length;\r\n    T sy = Vector!(T,3)(m.a21, m.a22, m.a23).length;\r\n    T sz = Vector!(T,3)(m.a31, m.a32, m.a33).length;\r\n    return Vector!(T,3)(sx, sy, sz);\r\n}\r\n\r\n/**\r\n * Create a matrix to perform a rotation about a world axis\r\n * (theta in radians)\r\n */\r\nMatrix!(T,4) rotationMatrix(T) (uint rotaxis, T theta)\r\ndo\r\n{\r\n    auto res = Matrix!(T,4).identity;\r\n\r\n    T s = sin(theta);\r\n    T c = cos(theta);\r\n\r\n    switch (rotaxis)\r\n    {\r\n        case Axis.x:\r\n            res.a11 = 1.0; res.a12 = 0.0; res.a13 = 0.0;\r\n            res.a21 = 0.0; res.a22 = c;   res.a23 =  s;\r\n            res.a31 = 0.0; res.a32 = -s;  res.a33 =  c;\r\n            break;\r\n\r\n        case Axis.y:\r\n            res.a11 = c;   res.a12 = 0.0; res.a13 = -s;\r\n            res.a21 = 0.0; res.a22 = 1.0; res.a23 = 0.0;\r\n            res.a31 = s;   res.a32 = 0.0; res.a33 = c;\r\n            break;\r\n\r\n        case Axis.z:\r\n            res.a11 = c;   res.a12 =  s;  res.a13 = 0.0;\r\n            res.a21 = -s;  res.a22 =  c;  res.a23 = 0.0;\r\n            res.a31 = 0.0; res.a32 = 0.0; res.a33 = 1.0;\r\n            break;\r\n\r\n        default:\r\n            assert(0);\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/**\r\n * Create a translation matrix given a translation vector\r\n */\r\nMatrix!(T,4) translationMatrix(T) (Vector!(T,3) v)\r\ndo\r\n{\r\n    auto res = Matrix!(T,4).identity;\r\n    res.a14 = v.x;\r\n    res.a24 = v.y;\r\n    res.a34 = v.z;\r\n    return res;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Matrix4f tm = translationMatrix(Vector3f(3.0f, 5.0f, 2.5f));\r\n    Vector3f t = translation(tm);\r\n    assert(t == Vector3f(3.0f, 5.0f, 2.5f));\r\n}\r\n\r\n/**\r\n * Create a matrix to perform scale on each axis\r\n */\r\nMatrix!(T,4) scaleMatrix(T) (Vector!(T,3) v)\r\ndo\r\n{\r\n    auto res = Matrix!(T,4).identity;\r\n    res.a11 = v.x;\r\n    res.a22 = v.y;\r\n    res.a33 = v.z;\r\n    return res;\r\n}\r\n\r\n/**\r\n * Create a combined transformation matrix from translation, rotation, and scaling.\r\n *\r\n * Params:\r\n *   t = Translation vector.\r\n *   r = Rotation quaternion.\r\n *   s = Scaling vector.\r\n * Returns:\r\n *   The resulting transformation matrix.\r\n */\r\nMatrix!(T,4) trsMatrix(T) (Vector!(T,3) t, Quaternion!T r, Vector!(T,3) s)\r\n{\r\n    Matrix!(T,4) res = Matrix!(T,4).identity;\r\n    Matrix!(T,3) rm = r.toMatrix3x3;\r\n    res.a11 = rm.a11 * s.x; res.a12 = rm.a12 * s.x; res.a13 = rm.a13 * s.x;\r\n    res.a21 = rm.a21 * s.y; res.a22 = rm.a22 * s.y; res.a23 = rm.a23 * s.y;\r\n    res.a31 = rm.a31 * s.z; res.a32 = rm.a32 * s.z; res.a33 = rm.a33 * s.z;\r\n    res.a14 = t.x;\r\n    res.a24 = t.y;\r\n    res.a34 = t.z;\r\n    return res;\r\n}\r\n\r\n/**\r\n * Setup the matrix to perform scale along an arbitrary axis\r\n */\r\nMatrix!(T,4) scaleAlongAxisMatrix(T) (Vector!(T,3) scaleAxis, T k)\r\nin\r\n{\r\n    assert (fabs (dot(scaleAxis, scaleAxis) - 1.0) < 0.001);\r\n}\r\ndo\r\n{\r\n    auto res = Matrix!(T,4).identity;\r\n\r\n    T a = k - 1.0;\r\n    T ax = a * scaleAxis.x;\r\n    T ay = a * scaleAxis.y;\r\n    T az = a * scaleAxis.z;\r\n\r\n    res.a11 = (ax * scaleAxis.x) + 1.0;\r\n    res.a22 = (ay * scaleAxis.y) + 1.0;\r\n    res.a33 = (az * scaleAxis.z) + 1.0;\r\n\r\n    res.a12 = res.a21 = (ax * scaleAxis.y);\r\n    res.a13 = res.a31 = (ax * scaleAxis.z);\r\n    res.a23 = res.a32 = (ay * scaleAxis.z);\r\n\r\n    return res;\r\n}\r\n\r\n/**\r\n * Create a matrix to perform uniform scale with respect to a point\r\n */\r\nMatrix!(T,4) homothetyMatrix(T) (Vector!(T,3) point, T scale)\r\ndo\r\n{\r\n    auto res = Matrix!(T,4).identity;\r\n    Vector!(T,3) t = point * (1.0 - scale);\r\n    res.a11 = scale; res.a14 = t.x;\r\n    res.a22 = scale; res.a24 = t.y;\r\n    res.a33 = scale; res.a34 = t.z;\r\n    return res;\r\n}\r\n\r\n/**\r\n * Setup the matrix to perform a shear\r\n */\r\nMatrix!(T,4) shearMatrix(T) (uint shearAxis, T s, T t)\r\ndo\r\n{\r\n    // NOTE: needs test\r\n    auto res = Matrix!(T,4).identity;\r\n\r\n    switch (shearAxis)\r\n    {\r\n        case Axis.x:\r\n            res.a11 = 1.0; res.a12 = 0.0; res.a13 = 0.0;\r\n            res.a21 = s;   res.a22 = 1.0; res.a23 = 0.0;\r\n            res.a31 = t;   res.a32 = 0.0; res.a33 = 1.0;\r\n            break;\r\n\r\n        case Axis.y:\r\n            res.a11 = 1.0; res.a12 = s;   res.a13 = 0.0;\r\n            res.a21 = 0.0; res.a22 = 1.0; res.a23 = 0.0;\r\n            res.a31 = 0.0; res.a32 = t;   res.a33 = 1.0;\r\n            break;\r\n\r\n        case Axis.z:\r\n            res.a11 = 1.0; res.a12 = 0.0; res.a13 = s;\r\n            res.a21 = 0.0; res.a22 = 1.0; res.a23 = t;\r\n            res.a31 = 0.0; res.a32 = 0.0; res.a33 = 1.0;\r\n            break;\r\n\r\n        default:\r\n            assert(0);\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/**\r\n * Setup the matrix to perform a projection onto a plane passing\r\n * through the origin. The plane is perpendicular to the\r\n * unit vector n.\r\n */\r\nMatrix!(T,4) projectionMatrix(T) (Vector!(T,3) n)\r\nin\r\n{\r\n    assert (fabs(dot(n, n) - 1.0) < 0.001);\r\n}\r\ndo\r\n{\r\n    // NOTE: needs test\r\n    auto res = Matrix!(T,4).identity;\r\n\r\n    res.a11 = 1.0 - (n.x * n.x);\r\n    res.a22 = 1.0 - (n.y * n.y);\r\n    res.a33 = 1.0 - (n.z * n.z);\r\n\r\n    res.a12 = res.a21 = -(n.x * n.y);\r\n    res.a13 = res.a31 = -(n.x * n.z);\r\n    res.a23 = res.a32 = -(n.y * n.z);\r\n\r\n    return res;\r\n}\r\n\r\n/**\r\n * Setup the matrix to perform a reflection about a plane parallel\r\n * to a cardinal plane.\r\n */\r\nMatrix!(T,4) reflectionMatrix(T) (Axis reflectionAxis, T k)\r\ndo\r\n{\r\n    auto res = Matrix!(T,4).identity;\r\n\r\n    switch (reflectionAxis)\r\n    {\r\n        case Axis.x:\r\n            res.a11 = -1.0; res.a21 =  0.0; res.a31 =  0.0; res.a41 = 2.0 * k;\r\n            res.a12 =  0.0; res.a22 =  1.0; res.a32 =  0.0; res.a42 = 0.0;\r\n            res.a13 =  0.0; res.a23 =  0.0; res.a33 =  1.0; res.a43 = 0.0;\r\n            break;\r\n\r\n        case Axis.y:\r\n            res.a11 =  1.0; res.a21 =  0.0; res.a31 =  0.0; res.a41 = 0.0;\r\n            res.a12 =  0.0; res.a22 = -1.0; res.a32 =  0.0; res.a42 = 2.0 * k;\r\n            res.a13 =  0.0; res.a23 =  0.0; res.a33 =  1.0; res.a43 = 0.0;\r\n            break;\r\n\r\n        case Axis.z:\r\n            res.a11 =  1.0; res.a21 =  0.0; res.a31 =  0.0; res.a41 = 0.0;\r\n            res.a12 =  0.0; res.a22 =  1.0; res.a32 =  0.0; res.a42 = 0.0;\r\n            res.a13 =  0.0; res.a23 =  0.0; res.a33 = -1.0; res.a43 = 2.0 * k;\r\n            break;\r\n\r\n        default:\r\n            assert(0);\r\n    }\r\n\r\n    return res;\r\n}\r\n\r\n/**\r\n * Setup the matrix to perform a reflection about an arbitrary plane\r\n * through the origin. The unit vector n is perpendicular to the plane.\r\n */\r\nMatrix!(T,4) axisReflectionMatrix(T) (Vector!(T,3) n)\r\nin\r\n{\r\n    assert (fabs(dot(n, n) - 1.0) < 0.001);\r\n}\r\ndo\r\n{\r\n    auto res = Matrix!(T,4).identity;\r\n\r\n    T ax = -2.0 * n.x;\r\n    T ay = -2.0 * n.y;\r\n    T az = -2.0 * n.z;\r\n\r\n    res.a11 = 1.0 + (ax * n.x);\r\n    res.a22 = 1.0 + (ay * n.y);\r\n    res.a32 = 1.0 + (az * n.z);\r\n\r\n    res.a12 = res.a21 = (ax * n.y);\r\n    res.a13 = res.a31 = (ax * n.z);\r\n    res.a23 = res.a32 = (ay * n.z);\r\n\r\n    return res;\r\n}\r\n\r\n/**\r\n * Setup the matrix to perform a \"Look At\" transformation\r\n * like a first person camera\r\n */\r\nMatrix!(T,4) lookAtMatrix(T) (Vector!(T,3) eye, Vector!(T,3) center, Vector!(T,3) up)\r\ndo\r\n{\r\n    auto Result = Matrix!(T,4).identity;\r\n\r\n    auto f = (center - eye).normalized;\r\n    auto u = (up).normalized;\r\n    auto s = cross(f, u).normalized;\r\n    assert(!s.isAlmostZero, \"look direction cannot be exactly parallel to the up direction\");\r\n    u = cross(s, f);\r\n\r\n    Result[0,0] = s.x;\r\n    Result[0,1] = s.y;\r\n    Result[0,2] = s.z;\r\n    Result[1,0] = u.x;\r\n    Result[1,1] = u.y;\r\n    Result[1,2] = u.z;\r\n    Result[2,0] =-f.x;\r\n    Result[2,1] =-f.y;\r\n    Result[2,2] =-f.z;\r\n    Result[0,3] =-dot(s, eye);\r\n    Result[1,3] =-dot(u, eye);\r\n    Result[2,3] = dot(f, eye);\r\n    return Result;\r\n}\r\n\r\n/**\r\n * Setup a frustum matrix given the left, right, bottom, top, near, and far\r\n * values for the frustum boundaries.\r\n */\r\nMatrix!(T,4) frustumMatrix(T) (T l, T r, T b, T t, T n, T f)\r\nin\r\n{\r\n    assert (n >= 0.0);\r\n    assert (f >= 0.0);\r\n}\r\ndo\r\n{\r\n    auto res = Matrix!(T,4).identity;\r\n\r\n    T width  = r - l;\r\n    T height = t - b;\r\n    T depth  = f - n;\r\n\r\n    res.arrayof[0] = (2 * n) / width;\r\n    res.arrayof[1] = 0.0;\r\n    res.arrayof[2] = 0.0;\r\n    res.arrayof[3] = 0.0;\r\n\r\n    res.arrayof[4] = 0.0;\r\n    res.arrayof[5] = (2 * n) / height;\r\n    res.arrayof[6] = 0.0;\r\n    res.arrayof[7] = 0.0;\r\n\r\n    res.arrayof[8] = (r + l) / width;\r\n    res.arrayof[9] = (t + b) / height;\r\n    res.arrayof[10]= -(f + n) / depth;\r\n    res.arrayof[11]= -1.0;\r\n\r\n    res.arrayof[12]= 0.0;\r\n    res.arrayof[13]= 0.0;\r\n    res.arrayof[14]= -(2 * f * n) / depth;\r\n    res.arrayof[15]= 0.0;\r\n\r\n    return res;\r\n}\r\n\r\n/**\r\n * Setup a perspective matrix given the field-of-view in the Y direction\r\n * in degrees, the aspect ratio of Y/X, and near and far plane distances\r\n */\r\nMatrix!(T,4) perspectiveMatrix(T) (T fovY, T aspect, T n, T f)\r\ndo\r\n{\r\n    auto res = Matrix!(T,4).identity;\r\n\r\n    T angle;\r\n    T cot;\r\n\r\n    angle = fovY / 2.0;\r\n    angle = degtorad(angle);\r\n\r\n    cot = cos(angle) / sin(angle);\r\n\r\n    res.arrayof[0] = cot / aspect;\r\n    res.arrayof[1] = 0.0;\r\n    res.arrayof[2] = 0.0;\r\n    res.arrayof[3] = 0.0;\r\n\r\n    res.arrayof[4] = 0.0;\r\n    res.arrayof[5] = cot;\r\n    res.arrayof[6] = 0.0;\r\n    res.arrayof[7] = 0.0;\r\n\r\n    res.arrayof[8] = 0.0;\r\n    res.arrayof[9] = 0.0;\r\n    res.arrayof[10]= -(f + n) / (f - n);\r\n    res.arrayof[11]= -1.0f; //-(2 * f * n) / (f - n);\r\n\r\n    res.arrayof[12]= 0.0;\r\n    res.arrayof[13]= 0.0;\r\n    res.arrayof[14]= -(2 * f * n) / (f - n); //-1.0;\r\n    res.arrayof[15]= 0.0;\r\n\r\n    return res;\r\n}\r\n\r\n/**\r\n * Setup an orthographic Matrix4x4 given the left, right, bottom, top, near,\r\n * and far values for the frustum boundaries.\r\n */\r\nMatrix!(T,4) orthoMatrix(T) (T l, T r, T b, T t, T n, T f)\r\ndo\r\n{\r\n    auto res = Matrix!(T,4).identity;\r\n\r\n    T width  = r - l;\r\n    T height = t - b;\r\n    T depth  = f - n;\r\n\r\n    res.arrayof[0] =  2.0 / width;\r\n    res.arrayof[1] =  0.0;\r\n    res.arrayof[2] =  0.0;\r\n    res.arrayof[3] =  0.0;\r\n\r\n    res.arrayof[4] =  0.0;\r\n    res.arrayof[5] =  2.0 / height;\r\n    res.arrayof[6] =  0.0;\r\n    res.arrayof[7] =  0.0;\r\n\r\n    res.arrayof[8] =  0.0;\r\n    res.arrayof[9] =  0.0;\r\n    res.arrayof[10]= -2.0 / depth;\r\n    res.arrayof[11]=  0.0;\r\n\r\n    res.arrayof[12]= -(r + l) / width;\r\n    res.arrayof[13]= -(t + b) / height;\r\n    res.arrayof[14]= -(f + n) / depth;\r\n    res.arrayof[15]=  1.0;\r\n\r\n    return res;\r\n}\r\n\r\n/**\r\n * Setup an orientation matrix using 3 basis normalized vectors\r\n */\r\nMatrix!(T,4) orthoNormalMatrix(T) (Vector!(T,3) xdir, Vector!(T,3) ydir, Vector!(T,3) zdir)\r\ndo\r\n{\r\n    auto res = Matrix!(T,4).identity;\r\n\r\n    res.arrayof[0] = xdir.x; res.arrayof[4] = ydir.x; res.arrayof[8] = zdir.x; res.arrayof[12] = 0.0;\r\n    res.arrayof[1] = xdir.y; res.arrayof[5] = ydir.y; res.arrayof[9] = zdir.y; res.arrayof[13] = 0.0;\r\n    res.arrayof[2] = xdir.z; res.arrayof[6] = ydir.z; res.arrayof[10]= zdir.z; res.arrayof[14] = 0.0;\r\n    res.arrayof[3] = 0.0;    res.arrayof[7] = 0.0;    res.arrayof[11]= 0.0;    res.arrayof[15] = 1.0;\r\n\r\n    return res;\r\n}\r\n\r\n/**\r\n * Setup a matrix that flattens geometry into a plane,\r\n * as if it were casting a shadow from a light\r\n */\r\nMatrix!(T,4) shadowMatrix(T) (Vector!(T,4) groundplane, Vector!(T,4) lightpos)\r\n{\r\n    T d = dot(groundplane, lightpos);\r\n\r\n    auto res = Matrix!(T,4).identity;\r\n\r\n    res.a11 = d-lightpos.x * groundplane.x;\r\n    res.a12 =  -lightpos.x * groundplane.y;\r\n    res.a13 =  -lightpos.x * groundplane.z;\r\n    res.a14 =  -lightpos.x * groundplane.w;\r\n\r\n    res.a21 =  -lightpos.y * groundplane.x;\r\n    res.a22 = d-lightpos.y * groundplane.y;\r\n    res.a23 =  -lightpos.y * groundplane.z;\r\n    res.a24 =  -lightpos.y * groundplane.w;\r\n\r\n    res.a31 =  -lightpos.z * groundplane.x;\r\n    res.a32 =  -lightpos.z * groundplane.y;\r\n    res.a33 = d-lightpos.z * groundplane.z;\r\n    res.a34 =  -lightpos.z * groundplane.w;\r\n\r\n    res.a41 =  -lightpos.w * groundplane.x;\r\n    res.a42 =  -lightpos.w * groundplane.y;\r\n    res.a43 =  -lightpos.w * groundplane.z;\r\n    res.a44 = d-lightpos.w * groundplane.w;\r\n\r\n    return res;\r\n}\r\n\r\n/**\r\n * Setup an orientation matrix using forward direction vector\r\n */\r\nMatrix!(T,4) directionToMatrix(T) (Vector!(T,3) zdir)\r\n{\r\n    Vector!(T,3) xdir = Vector!(T,3)(0, 0, 1);\r\n    Vector!(T,3) ydir;\r\n    float d = zdir.z;\r\n\r\n    if (d > -0.999999999 && d < 0.999999999)\r\n    {\r\n        xdir = xdir - zdir * d;\r\n        xdir.normalize();\r\n        ydir = cross(zdir, xdir);\r\n    }\r\n    else\r\n    {\r\n        xdir = Vector!(T,3)(zdir.z, 0, -zdir.x);\r\n        ydir = Vector!(T,3)(0, 1, 0);\r\n    }\r\n\r\n    auto m = Matrix!(T,4).identity;\r\n\r\n    m.a13 = zdir.x;\r\n    m.a23 = zdir.y;\r\n    m.a33 = zdir.z;\r\n\r\n    m.a11 = xdir.x;\r\n    m.a21 = xdir.y;\r\n    m.a31 = xdir.z;\r\n\r\n    m.a12 = ydir.x;\r\n    m.a22 = ydir.y;\r\n    m.a32 = ydir.z;\r\n\r\n    return m;\r\n}\r\n\r\n/**\r\n * Setup an orientation matrix that performs rotation\r\n * between two vectors\r\n *\r\n * Currently this is just a shortcut\r\n * for dlib.math.quaternion.rotationBetween\r\n */\r\nMatrix!(T,4) rotationBetweenVectors(T) (Vector!(T,3) source, Vector!(T,3) target)\r\n{\r\n    return rotationBetween(source, target).toMatrix4x4;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    bool isAlmostZero4(Vector4f v)\r\n    {\r\n        float e = 0.002f;\r\n\r\n        return abs(v.x) < e &&\r\n               abs(v.y) < e &&\r\n               abs(v.z) < e &&\r\n               abs(v.w) < e;\r\n    }\r\n\r\n    // build ModelView (World to Camera)\r\n    vec3 center = vec3(0.0f, 0.0f, 0.0f);\r\n    vec3 eye = center + vec3(0.0f, 1.0f, 1.0f);\r\n    vec3 up = vec3(0.0f, -0.707f, 0.707f);\r\n\r\n    Matrix4f modelView = lookAtMatrix(eye, center, up);\r\n\r\n    // build Projection (Camera to Eye)\r\n    Matrix4f projection = perspectiveMatrix(45.0f, 16.0f / 9.0f, 1.0f, 100.0f);\r\n\r\n    // compose into one transformation\r\n    Matrix4f projectionModelView = projection * modelView;\r\n\r\n    vec4 positionInWorld = vec4(0.0f, 0.0f, 0.0f, 1.0f);\r\n\r\n    vec4 transformed1 =\r\n        positionInWorld * projectionModelView;\r\n\r\n    vec4 transformed2 =\r\n        (positionInWorld * modelView) * projection;\r\n\r\n    assert(isAlmostZero4(transformed1 - transformed2));\r\n}\r\n\r\n/**\r\n * Affine transformations in 2D space\r\n */\r\n Vector!(T,2) affineTransform2D(T)(Vector!(T,2) v, Matrix!(T,3) m)\r\n{\r\n    return Vector!(T,2)\r\n    (\r\n        (v.x * m.a11) + (v.y * m.a12) + m.a13,\r\n        (v.x * m.a21) + (v.y * m.a22) + m.a23\r\n    );\r\n}\r\n\r\n/**\r\n * Translation in 2D space\r\n */\r\nMatrix!(T,3) translationMatrix2D(T) (Vector!(T,2) t)\r\ndo\r\n{\r\n    Matrix!(T,3) res;\r\n    res.a11 = 1; res.a12 = 0; res.a13 = t.x;\r\n    res.a21 = 0; res.a22 = 1; res.a23 = t.y;\r\n    res.a31 = 0; res.a32 = 0; res.a33 = 1;\r\n    return res;\r\n}\r\n\r\nalias translation2 = translationMatrix2D;\r\n\r\n/**\r\n * Rotation in 2D space\r\n */\r\nMatrix!(T,3) rotationMatrix2D(T) (T theta)\r\ndo\r\n{\r\n    Matrix!(T,3) res;\r\n    T s = sin(theta);\r\n    T c = cos(theta);\r\n    res.a11 = c;  res.a12 = s; res.a13 = 0;\r\n    res.a21 = -s; res.a22 = c; res.a23 = 0;\r\n    res.a31 = 0;  res.a32 = 0; res.a33 = 1;\r\n    return res;\r\n}\r\n\r\nalias rotation2 = rotationMatrix2D;\r\n\r\n/**\r\n * Scale in 2D space\r\n */\r\nMatrix!(T,3) scaleMatrix2D(T) (Vector!(T,2) s)\r\ndo\r\n{\r\n    Matrix!(T,3) res;\r\n    res.a11 = s.x; res.a12 = 0;   res.a13 = 0;\r\n    res.a21 = 0;   res.a22 = s.y; res.a23 = 0;\r\n    res.a31 = 0;   res.a32 = 0;   res.a33 = 1;\r\n    return res;\r\n}\r\n\r\nalias scale2 = scaleMatrix2D;\r\n\r\n/**\r\n * Homothety (scale with respect to a point) in 2D space\r\n */\r\nMatrix!(T,3) homothetyMatrix2D(T) (Vector!(T,2) point, T scale)\r\ndo\r\n{\r\n    auto res = Matrix!(T,3).identity;\r\n    Vector!(T,2) t = point * (1.0 - scale);\r\n    res.a11 = scale; res.a13 = t.x;\r\n    res.a22 = scale; res.a23 = t.y;\r\n    return res;\r\n}\r\n\r\nalias homothety2 = homothetyMatrix2D;\n\r\n///\r\nunittest\r\n{\r\n    bool isAlmostZero2(Vector2f v)\r\n    {\r\n        float e = 0.002f;\r\n\r\n        return abs(v.x) < e &&\r\n               abs(v.y) < e;\r\n    }\r\n    \r\n    vec2 v = vec2(1, 0);\r\n    Matrix3f tm = translationMatrix2D(vec2(2, 3));\r\n    vec2 vt = affineTransform2D(v, tm);\r\n    assert(isAlmostZero2(vt - vec2(3, 3)));\r\n    \r\n    Matrix3f rm = rotationMatrix2D(cast(float)PI);\r\n    vt = affineTransform2D(v, rm);\r\n    assert(isAlmostZero2(vt - vec2(-1, 0)));\r\n    \r\n    Matrix3f sm = scaleMatrix2D(vec2(2, 2));\r\n    vt = affineTransform2D(v, sm);\r\n    assert(isAlmostZero2(vt - vec2(2, 0)));\r\n}\r\n"
  },
  {
    "path": "dlib/math/utils.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Utility math functions\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.math.utils;\r\n\r\nprivate\r\n{\r\n    import core.stdc.stdlib;\r\n    import std.math;\r\n}\r\n\r\npublic:\r\n\r\n/**\r\n * Very small value\r\n */\r\nenum EPSILON = 0.000001;\r\n\r\n/**\r\n * Axes of Cartesian space\r\n */\r\nenum Axis\r\n{\r\n    x = 0, y = 1, z = 2\r\n}\r\n\r\n/**\r\n * Convert degrees to radians\r\n */\r\nT degtorad(T) (T angle) nothrow\r\n{\r\n    return (angle / 180.0) * PI;\r\n}\r\n\r\n/**\r\n * Convert radians to degrees\r\n */\r\nT radtodeg(T) (T angle) nothrow\r\n{\r\n    return (angle / PI) * 180.0;\r\n}\r\n\r\n/**\r\n * Convert radians to revolutions\r\n */\r\nT radtorev(T)(T angle) nothrow\r\n{\r\n    return angle / (2.0 * PI);\r\n}\r\n\r\n/**\r\n * Convert revolutions to radians\r\n */\r\nT revtorad(T)(angle) nothrow\r\n{\r\n    return angle * (2.0 * PI);\r\n}\n\n/**\r\n * Find maximum of two values\r\n */\r\nT max2(T) (T x, T y) nothrow\r\n{\r\n    return (x > y)? x : y;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(max2(2, 1) == 2);\r\n}\r\n\r\n/**\r\n * Find minimum of two values\r\n */\r\nT min2(T) (T x, T y) nothrow\r\n{\r\n    return (x < y)? x : y;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(min2(2, 1) == 1);\r\n}\r\n\r\n/**\r\n * Find maximum of three values\r\n */\r\nT max3(T) (T x, T y, T z) nothrow\r\n{\r\n    T temp = (x > y)? x : y;\r\n    return (temp > z) ? temp : z;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(max3(3, 2, 1) == 3);\r\n}\r\n\r\n/**\r\n * Find minimum of three values\r\n */\r\nT min3(T) (T x, T y, T z) nothrow\r\n{\r\n    T temp = (x < y)? x : y;\r\n    return (temp < z) ? temp : z;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(min3(3, 2, 1) == 1);\r\n}\r\n\r\n/**\r\n * Limit to given range\r\n */\r\nstatic if (__traits(compiles, (){import std.algorithm: clamp;}))\r\n{\r\n    public import std.algorithm: clamp;\r\n}\r\nelse\r\n{\r\n    T clamp(T) (T v, T minimal, T maximal) nothrow\r\n    {\r\n        if (v > minimal)\r\n        {\r\n            if (v < maximal) return v;\r\n                else return maximal;\r\n        }\r\n        else return minimal;\r\n    }\r\n}\r\n\r\n/**\r\n * Is less than EPSILON\r\n */\r\nbool isConsiderZero(T) (T f) nothrow\r\n{\r\n    return (abs(f) < EPSILON);\r\n}\r\n\r\n/**\r\n * Is power of 2\r\n */\r\nbool isPowerOfTwo(T)(T x) nothrow\r\n{\r\n    return (x != 0) && ((x & (x - 1)) == 0);\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(isPowerOfTwo(16));\r\n    assert(!isPowerOfTwo(20));\r\n}\r\n\r\n/**\r\n * Round to next power of 2\r\n */\r\nT nextPowerOfTwo(T) (T k) nothrow\r\n{\r\n    if (k == 0)\r\n        return 1;\r\n    k--;\r\n    for (T i = 1; i < T.sizeof * 8; i <<= 1)\r\n        k = k | k >> i;\r\n    return k + 1;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(nextPowerOfTwo(0) == 1);\r\n    assert(nextPowerOfTwo(5) == 8);\r\n}\r\n\r\n/**\r\n * Round to next power of 10\r\n */\r\nT nextPowerOfTen(T) (T k) nothrow\r\n{\r\n    return pow(10, cast(int)ceil(log10(k)));\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(nextPowerOfTen!double(80) == 100);\r\n}\r\n\r\n/**\r\n * If at least one element is zero\r\n */\r\nbool oneOfIsZero(T) (T[] array...) nothrow\r\n{\r\n    foreach(i, v; array)\r\n        if (v == 0) return true;\r\n    return false;\r\n}\r\n\r\n/**\r\n * Byte operations\r\n */\r\nversion (BigEndian)\r\n{\r\n    ushort bigEndian(ushort value) nothrow\r\n    {\r\n        return value;\r\n    }\r\n    \r\n    ///\r\n    unittest\r\n    {\r\n        assert(bigEndian(cast(ushort)0x00FF) == 0x00FF);\r\n    }\n\n    uint bigEndian(uint value) nothrow\r\n    {\r\n        return value;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        assert(bigEndian(cast(uint)0x000000FF) == cast(uint)0x000000FF);\r\n    }\r\n\r\n    ushort networkByteOrder(ushort value) nothrow\r\n    {\r\n        return value;\r\n    }\n\r\n    ///\r\n    unittest\r\n    {\r\n        assert(networkByteOrder(cast(ushort)0x00FF) == 0x00FF);\r\n    }\r\n\n    uint networkByteOrder(uint value) nothrow\r\n    {\r\n        return value;\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        assert(networkByteOrder(cast(uint)0x000000FF) == cast(uint)0x000000FF);\r\n    }\r\n}\r\n\r\nversion (LittleEndian)\r\n{\n    ushort bigEndian(ushort value) nothrow\r\n    {\r\n        return ((value & 0xFF) << 8) | ((value >> 8) & 0xFF);\r\n    }\n\r\n    ///\r\n    unittest\r\n    {\r\n        assert(bigEndian(cast(ushort)0x00FF) == 0xFF00);\r\n    }\r\n\r\n    uint bigEndian(uint value) nothrow\r\n    {\r\n        return value << 24\r\n            | (value & 0x0000FF00) << 8\r\n            | (value & 0x00FF0000) >> 8\r\n            |  value >> 24;\r\n    }\n\r\n    ///\r\n    unittest\r\n    {\r\n        assert(bigEndian(cast(uint)0x000000FF) == cast(uint)0xFF000000);\r\n    }\r\n\n    ushort networkByteOrder(ushort value) nothrow\r\n    {\r\n        return bigEndian(value);\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        assert(networkByteOrder(cast(ushort)0x00FF) == 0xFF00);\r\n    }\r\n\r\n    uint networkByteOrder(uint value) nothrow\r\n    {\r\n        return bigEndian(value);\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        assert(networkByteOrder(cast(uint)0x000000FF) == cast(uint)0xFF000000);\r\n    }\r\n}\r\n\r\n/**\r\n * Returns 16-bit integer n with swapped endianness\r\n */\r\nT swapEndian16(T)(T n)\r\n{\r\n    return cast(T)((n >> 8) | (n << 8));\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(swapEndian16(cast(ushort)0xFF00) == 0x00FF);\r\n}\r\n\r\n/**\r\n * Constructs uint from an array of bytes\r\n */\r\nuint bytesToUint(ubyte[4] src) nothrow\r\n{\r\n    return (src[0] << 24 | src[1] << 16 | src[2] << 8 | src[3]);\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(bytesToUint([0xee, 0x10, 0xab, 0xff]) == 0xee10abff);\r\n}\r\n\r\n/**\r\n * Field of view angle Y from X\r\n */\r\nT fovYfromX(T) (T xfov, T aspectRatio) nothrow\r\n{\r\n    xfov = degtorad(xfov);\r\n    T yfov = 2.0 * atan(tan(xfov * 0.5)/aspectRatio);\r\n    return radtodeg(yfov);\r\n}\r\n\r\n/**\r\n * Field of view angle X from Y\r\n */\r\nT fovXfromY(T) (T yfov, T aspectRatio) nothrow\r\n{\r\n    yfov = degtorad(yfov);\r\n    T xfov = 2.0 * atan(tan(yfov * 0.5) * aspectRatio);\r\n    return radtodeg(xfov);\r\n}\r\n\r\n/**\r\n * Sign of a number\r\n */\r\nint sign(T)(T x) nothrow\r\n{\r\n    return (x > 0) - (x < 0);\r\n}\r\n\r\n/**\r\n * Swap values\r\n */\r\nvoid swap(T)(T* a, T* b)\r\n{\r\n    T c = *a;\r\n    *a = *b;\r\n    *b = c;\r\n}\r\n\r\n/**\r\n * Is perfect square\r\n */\r\nbool isPerfectSquare(float n) nothrow\r\n{\r\n    float r = sqrt(n);\r\n    return(r * r == n);\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(isPerfectSquare(64.0f));\r\n}\r\n\r\n/**\r\n * Integer part\r\n */\r\nreal integer(real v)\r\n{\r\n    real ipart;\r\n    modf(v, ipart);\r\n    return ipart;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(integer(54.832f) == 54.0f);\r\n}\r\n\r\n/**\r\n * Fractional part\r\n */\r\nreal frac(real v)\r\n{\r\n    real ipart;\r\n    return modf(v, ipart);\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(abs(frac(54.832f) - 0.832f) <= EPSILON);\r\n}\r\n\r\n/** \r\n * Wraps an arbitrary angle to [-180, +180] degrees range.\r\n *\r\n * Params:\r\n *   a = angle in degrees.\r\n * Returns:\r\n *   Wrapped angle in degrees.\r\n */\r\nT wrapAngle(T)(T a)\r\n{\r\n    a = fmod(a + 180.0, 360.0);\r\n    if (a < 0.0)\r\n        a += 360.0;\r\n    return a - 180.0;\r\n}\r\n"
  },
  {
    "path": "dlib/math/vector.d",
    "content": "/*\r\nCopyright (c) 2011-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Vectors of Euclidean space\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.math.vector;\r\n\r\nimport std.conv;\r\nimport std.math;\r\nimport std.random;\r\nimport std.range;\r\nimport std.format;\r\nimport std.traits;\r\nimport dlib.core.tuple;\r\nimport dlib.math.utils;\r\nimport dlib.math.matrix;\r\n\r\n/**\r\n * Vector representation\r\n */\r\nstruct Vector(T, int size)\r\n{\r\n    public:\r\n\r\n   /**\r\n    * Vector constructor.\r\n    * Supports initializing from vector of arbitrary length and type\r\n    */\r\n    this (T2, int size2)(Vector!(T2, size2) v)\r\n    {\r\n        if (v.arrayof.length >= size)\r\n        {\r\n            foreach(i; 0..size)\r\n                arrayof[i] = cast(T)v.arrayof[i];\r\n        }\r\n        else\r\n        {\r\n            foreach(i; 0..v.arrayof.length)\r\n                arrayof[i] = cast(T)v.arrayof[i];\r\n        }\r\n    }\r\n\r\n   /**\r\n    * Array constructor\r\n    */\r\n    this (A)(A components) if (isDynamicArray!A && !isSomeString!A)\r\n    {\r\n        if (components.length >= size)\r\n        {\r\n            foreach(i; 0..size)\r\n                arrayof[i] = cast(T)components[i];\r\n        }\r\n        else\r\n        {\r\n            foreach(i; 0..components.length)\r\n                arrayof[i] = cast(T)components[i];\r\n        }\r\n    }\r\n\r\n   /**\r\n    * Static array constructor\r\n    */\r\n    this (T2, size_t arrSize)(T2[arrSize] components)\r\n    {\r\n        if (components.length >= size)\r\n        {\r\n            foreach(i; 0..size)\r\n                arrayof[i] = cast(T)components[i];\r\n        }\r\n        else\r\n        {\r\n            foreach(i; 0..components.length)\r\n                arrayof[i] = cast(T)components[i];\r\n        }\r\n    }\r\n\r\n   /**\r\n    * Tuple constructor\r\n    */\r\n    this (F...)(F components)\r\n    {\r\n        foreach(i; RangeTuple!(0, size))\r\n        {\r\n            static if (i < components.length)\r\n                arrayof[i] = cast(T)components[i];\r\n            else\r\n                arrayof[i] = cast(T)components[$-1];\r\n        }\r\n    }\r\n\r\n   /**\r\n    * String constructor\r\n    */\r\n    this (S)(S str) if (isSomeString!S)\r\n    {\r\n        arrayof = parse!(T[size])(str);\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) = Vector!(T,size2)\r\n    */\r\n    void opAssign(T2, int size2)(Vector!(T2,size2) v)\r\n    {\r\n        if (v.arrayof.length >= size)\r\n        {\r\n            foreach(i; 0..size)\r\n                arrayof[i] = cast(T)v.arrayof[i];\r\n        }\r\n        else\r\n        {\r\n            foreach(i; 0..v.arrayof.length)\r\n                arrayof[i] = cast(T)v.arrayof[i];\r\n        }\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) = Vector!(T,size2) for enums\r\n    */\r\n    void opAssign(T2)(T2 ev) if (is(T2 == enum))\r\n    {\r\n        opAssign(cast(OriginalType!T2)ev);\r\n    }\r\n\r\n   /**\r\n    * -Vector!(T,size)\r\n    */\r\n    Vector!(T,size) opUnary(string s) () const if (s == \"-\")\r\n    do\r\n    {\r\n        Vector!(T,size) res;\r\n        foreach(i; RangeTuple!(0, size))\r\n            res.arrayof[i] = -arrayof[i];\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * +Vector!(T,size)\r\n    */\r\n    Vector!(T,size) opUnary(string s) () const if (s == \"+\")\r\n    do\r\n    {\r\n        return Vector!(T,size)(this);\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) + Vector!(T,size)\r\n    */\r\n    Vector!(T,size) opBinary(string op)(Vector!(T,size) v) const if (op == \"+\")\r\n    do\r\n    {\r\n        Vector!(T,size) res;\r\n        foreach(i; RangeTuple!(0, size))\r\n            res.arrayof[i] = cast(T)(arrayof[i] + v.arrayof[i]);\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) - Vector!(T,size)\r\n    */\r\n    Vector!(T,size) opBinary(string op)(Vector!(T,size) v) const if (op == \"-\")\r\n    do\r\n    {\r\n        Vector!(T,size) res;\r\n        foreach(i; RangeTuple!(0, size))\r\n            res.arrayof[i] = cast(T)(arrayof[i] - v.arrayof[i]);\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) * Vector!(T,size)\r\n    */\r\n    Vector!(T,size) opBinary(string op)(Vector!(T,size) v) const if (op == \"*\")\r\n    do\r\n    {\r\n        Vector!(T,size) res;\r\n        foreach(i; RangeTuple!(0, size))\r\n            res.arrayof[i] = cast(T)(arrayof[i] * v.arrayof[i]);\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) / Vector!(T,size)\r\n    */\r\n    Vector!(T,size) opBinary(string op)(Vector!(T,size) v) const if (op == \"/\")\r\n    do\r\n    {\r\n        Vector!(T,size) res;\r\n        foreach(i; RangeTuple!(0, size))\r\n            res.arrayof[i] = cast(T)(arrayof[i] / v.arrayof[i]);\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) + T\r\n    */\r\n    Vector!(T,size) opBinary(string op)(T t) const if (op == \"+\")\r\n    do\r\n    {\r\n        Vector!(T,size) res;\r\n        foreach(i; RangeTuple!(0, size))\r\n            res.arrayof[i] = cast(T)(arrayof[i] + t);\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) - T\r\n    */\r\n    Vector!(T,size) opBinary(string op)(T t) const if (op == \"-\")\r\n    do\r\n    {\r\n        Vector!(T,size) res;\r\n        foreach(i; RangeTuple!(0, size))\r\n            res.arrayof[i] = cast(T)(arrayof[i] - t);\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) * T\r\n    */\r\n    Vector!(T,size) opBinary(string op)(T t) const if (op == \"*\")\r\n    do\r\n    {\r\n        Vector!(T,size) res;\r\n        foreach(i; RangeTuple!(0, size))\r\n            res.arrayof[i] = cast(T)(arrayof[i] * t);\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * T * Vector!(T,size)\r\n    */\r\n    Vector!(T,size) opBinaryRight(string op) (T t) const\r\n        if (op == \"*\" && isNumeric!T)\r\n    do\r\n    {\r\n        Vector!(T,size) res;\r\n        foreach(i; RangeTuple!(0, size))\r\n            res.arrayof[i] = cast(T)(arrayof[i] * t);\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) / T\r\n    */\r\n    Vector!(T,size) opBinary(string op)(T t) const if (op == \"/\")\r\n    do\r\n    {\r\n        Vector!(T,size) res;\r\n        foreach(i; RangeTuple!(0, size))\r\n            res.arrayof[i] = cast(T)(arrayof[i] / t);\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) % T\r\n    */\r\n    Vector!(T,size) opBinary(string op, T2) (T2 t) const if (op == \"%\")\r\n    do\r\n    {\r\n        Vector!(T,size) res;\r\n        foreach(i; RangeTuple!(0, size))\r\n            res.arrayof[i] = cast(T)(arrayof[i] % t);\r\n        return res;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) += Vector!(T,size)\r\n    */\r\n    Vector!(T,size) opOpAssign(string op)(Vector!(T,size) v) if (op == \"+\")\r\n    do\r\n    {\r\n        foreach(i; RangeTuple!(0, size))\r\n            arrayof[i] += v.arrayof[i];\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) -= Vector!(T,size)\r\n    */\r\n    Vector!(T,size) opOpAssign(string op)(Vector!(T,size) v) if (op == \"-\")\r\n    do\r\n    {\r\n        foreach(i; RangeTuple!(0, size))\r\n            arrayof[i] -= v.arrayof[i];\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) *= Vector!(T,size)\r\n    */\r\n    Vector!(T,size) opOpAssign(string op)(Vector!(T,size) v) if (op == \"*\")\r\n    do\r\n    {\r\n        foreach(i; RangeTuple!(0, size))\r\n            arrayof[i] *= v.arrayof[i];\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) /= Vector!(T,size)\r\n    */\r\n    Vector!(T,size) opOpAssign(string op)(Vector!(T,size) v) if (op == \"/\")\r\n    do\r\n    {\r\n        foreach(i; RangeTuple!(0, size))\r\n            arrayof[i] /= v.arrayof[i];\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) += T\r\n    */\r\n    Vector!(T,size) opOpAssign(string op)(T t) if (op == \"+\")\r\n    do\r\n    {\r\n        foreach(i; RangeTuple!(0, size))\r\n            arrayof[i] += t;\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) -= T\r\n    */\r\n    Vector!(T,size) opOpAssign(string op)(T t) if (op == \"-\")\r\n    do\r\n    {\r\n        foreach(i; RangeTuple!(0, size))\r\n            arrayof[i] -= t;\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) *= T\r\n    */\r\n    Vector!(T,size) opOpAssign(string op)(T t) if (op == \"*\")\r\n    do\r\n    {\r\n        foreach(i; RangeTuple!(0, size))\r\n            arrayof[i] *= t;\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) /= T\r\n    */\r\n    Vector!(T,size) opOpAssign(string op)(T t) if (op == \"/\")\r\n    do\r\n    {\r\n        foreach(i; RangeTuple!(0, size))\r\n            arrayof[i] /= t;\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size) %= T\r\n    */\r\n    Vector!(T,size) opOpAssign(string op, T2)(T2 t) if (op == \"%\")\r\n    do\r\n    {\r\n        foreach(i; RangeTuple!(0, size))\r\n            arrayof[i] %= t;\r\n        return this;\r\n    }\r\n\r\n   /**\r\n    * T = Vector!(T,size)[index]\r\n    */\r\n    auto ref T opIndex(this X)(size_t index)\r\n    in\r\n    {\r\n        assert ((0 <= index) && (index < size),\r\n            \"Vector!(T,size).opIndex(int index): array index out of bounds\");\r\n    }\r\n    do\r\n    {\r\n        return arrayof[index];\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size)[index] = T\r\n    */\r\n    void opIndexAssign(T n, size_t index)\r\n    in\r\n    {\r\n        assert ((0 <= index) && (index < size),\r\n            \"Vector!(T,size).opIndexAssign(int index): array index out of bounds\");\r\n    }\r\n    do\r\n    {\r\n        arrayof[index] = n;\r\n    }\r\n\r\n   /**\r\n    * T[] = Vector!(T,size)[index1..index2]\r\n    */\r\n    auto opSlice(this X)(size_t index1, size_t index2)\r\n    in\r\n    {\r\n        assert ((0 <= index1) || (index1 < 3) || (0 <= index2) || (index2 < 3) || (index1 < index2),\r\n            \"Vector!(T,size).opSlice(int index1, int index2): array index out of bounds\");\r\n    }\r\n    do\r\n    {\r\n        return arrayof[index1..index2];\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size)[index1..index2] = T\r\n    */\r\n    T opSliceAssign(T t, size_t index1, size_t index2)\r\n    in\r\n    {\r\n        assert ((0 <= index1) || (index1 < 3) || (0 <= index2) || (index2 < 3) || (index1 < index2),\r\n            \"Vector!(T,size).opSliceAssign(T t, int index1, int index2): array index out of bounds\");\r\n    }\r\n    do\r\n    {\r\n        arrayof[index1..index2] = t;\r\n        return t;\r\n    }\r\n\r\n   /**\r\n    * T = Vector!(T,size)[]\r\n    */\r\n    auto opSlice(this X)()\r\n    do\r\n    {\r\n        return arrayof[];\r\n    }\r\n\r\n   /**\r\n    * Vector!(T,size)[] = T\r\n    */\r\n    T opSliceAssign(T t)\r\n    do\r\n    {\r\n        foreach(i; RangeTuple!(0, size))\r\n            arrayof[i] = t;\r\n        return t;\r\n    }\r\n\r\n    static if (isNumeric!(T))\r\n    {\r\n       /**\r\n        * Get vector length squared\r\n        */\r\n        @property T lengthsqr() const\r\n        do\r\n        {\r\n            T res = 0;\r\n            foreach (component; arrayof)\r\n                res += component * component;\r\n            return res;\r\n        }\r\n\r\n       /**\r\n        * Get vector length\r\n        */\r\n        @property T length() const\r\n        do\r\n        {\r\n            static if (isFloatingPoint!T)\r\n            {\r\n                T t = 0;\r\n                foreach (component; arrayof)\r\n                    t += component * component;\r\n                return sqrt(t);\r\n            }\r\n            else\r\n            {\r\n                T t = 0;\r\n                foreach (component; arrayof)\r\n                    t += component * component;\r\n                return cast(T)sqrt(cast(float)t);\r\n            }\r\n        }\r\n\r\n       /**\r\n        * Set vector length to 1\r\n        */\r\n        void normalize()\r\n        do\r\n        {\r\n            static if (isFloatingPoint!T)\r\n            {\r\n                T lensqr = lengthsqr();\r\n                if (lensqr > 0)\r\n                {\r\n                    T coef = 1.0 / sqrt(lensqr);\r\n                    foreach (ref component; arrayof)\r\n                        component *= coef;\r\n                }\r\n            }\r\n            else\r\n            {\r\n                T lensqr = lengthsqr();\r\n                if (lensqr > 0)\r\n                {\r\n                    float coef = 1.0 / sqrt(cast(float)lensqr);\r\n                    foreach (ref component; arrayof)\r\n                        component = cast(T)(component * coef);\r\n                }\r\n            }\r\n        }\r\n\r\n       /**\r\n        * Return normalized copy\r\n        */\r\n        @property Vector!(T,size) normalized() const\r\n        do\r\n        {\r\n            Vector!(T,size) res = this;\r\n            res.normalize();\r\n            return res;\r\n        }\r\n\r\n       /**\r\n        * Return true if all components are zero\r\n        */\r\n        @property bool isZero() const\r\n        do\r\n        {\r\n            foreach(i; RangeTuple!(0, size))\r\n                if (arrayof[i] != 0)\r\n                    return false;\r\n            return true;\r\n        }\r\n\r\n       /**\r\n        * Clamp components to min/max value\r\n        */\r\n        void clamp(T minv, T maxv)\r\n        {\r\n            foreach (ref v; arrayof)\r\n                v = .clamp(v, minv, maxv);\r\n        }\r\n    }\r\n\r\n   /**\r\n    * Convert to string\r\n    */\r\n    @property string toString() const\r\n    do\r\n    {\r\n        auto writer = appender!string();\r\n        formattedWrite(writer, \"%s\", arrayof);\r\n        return writer.data;\r\n    }\r\n\r\n   /**\r\n    * Swizzling\r\n    */\r\n    template opDispatch(string s) if (valid(s))\r\n    {\r\n        static if (s.length <= 4)\r\n        {\r\n            private static auto extend(string s)\r\n            {\r\n                while (s.length < 4)\r\n                    s ~= s[$-1];\r\n                return s;\r\n            }\r\n\r\n            unittest\r\n            {\r\n                assert(extend(\"x\") == \"xxxx\");\r\n            }\r\n\r\n            @property auto ref opDispatch(this X)()\r\n            {\r\n                enum p = extend(s);\r\n                enum i = (char c) => ['x':0, 'y':1, 'z':2, 'w':3,\r\n                                      'r':0, 'g':1, 'b':2, 'a':3,\r\n                                      's':0, 't':1, 'p':2, 'q':3][c];\r\n                enum i0 = i(p[0]),\r\n                     i1 = i(p[1]),\r\n                     i2 = i(p[2]),\r\n                     i3 = i(p[3]);\r\n\r\n                static if (s.length == 4)\r\n                    return Vector!(T,4)(arrayof[i0], arrayof[i1], arrayof[i2], arrayof[i3]);\r\n                else static if (s.length == 3)\r\n                    return Vector!(T,3)(arrayof[i0], arrayof[i1], arrayof[i2]);\r\n                else static if (s.length == 2)\r\n                    return Vector!(T,2)(arrayof[i0], arrayof[i1]);\r\n            }\r\n\r\n            @property void opDispatch(this X, T2, alias n)(Vector!(T2, n) vec)\r\n                if (s.length == n)\r\n            {\r\n                enum p = extend(s);\r\n                enum i = (char c) => ['x':0, 'y':1, 'z':2, 'w':3,\r\n                                      'r':0, 'g':1, 'b':2, 'a':3,\r\n                                      's':0, 't':1, 'p':2, 'q':3][c];\r\n                enum i0 = i(p[0]),\r\n                     i1 = i(p[1]),\r\n                     i2 = i(p[2]),\r\n                     i3 = i(p[3]);\r\n\r\n                static if (s.length == 4)\r\n                {\r\n                    arrayof[i3] = vec.arrayof[3];\r\n                }\r\n\r\n                static if (s.length >= 3)\r\n                {\r\n                    arrayof[i2] = vec.arrayof[2];\r\n                }\r\n\r\n                static if (s.length >= 2)\r\n                {\r\n                    arrayof[i1] = vec.arrayof[1];\r\n                    arrayof[i0] = vec.arrayof[0];\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    private static bool valid(string s)\r\n    {\r\n        if (s.length < 2)\r\n            return false;\r\n\r\n        foreach(c; s)\r\n        {\r\n            switch(c)\r\n            {\r\n                case 'w', 'a', 'q':\r\n                    if (size < 4) return false;\r\n                    else break;\r\n                case 'z', 'b', 'p':\r\n                    if (size < 3) return false;\r\n                    else break;\r\n                case 'y', 'g', 't':\r\n                    if (size < 2) return false;\r\n                    else break;\r\n                case 'x', 'r', 's':\r\n                    if (size < 1) return false;\r\n                    else break;\r\n                default:\r\n                    return false;\r\n            }\r\n        }\r\n        return true;\r\n    }\r\n\r\n    unittest\r\n    {\r\n        static if (size == 3)\r\n        {\r\n            assert(valid(\"xyz\"));\r\n            assert(valid(\"rgb\"));\r\n            assert(valid(\"stp\"));\r\n            assert(!valid(\"m\"));\r\n            assert(!valid(\"km\"));\r\n        }\r\n        else static if (size == 4)\r\n        {\r\n            assert(valid(\"xyzw\"));\r\n            assert(valid(\"rgba\"));\r\n            assert(valid(\"stpq\"));\r\n        }\r\n    }\r\n\r\n   /**\r\n    * Symbolic element access\r\n    */\r\n    private static string elements(string[4] letters) @property\r\n    do\r\n    {\r\n        string res;\r\n        foreach (i; 0..size)\r\n        {\r\n            res ~= \"T \" ~ letters[i] ~ \"; \";\r\n        }\r\n        return res;\r\n    }\r\n\r\n    unittest\r\n    {\r\n        static if (size == 3)\r\n        {\r\n            assert(elements([\"x\", \"y\", \"z\", \"w\"]) == \"T x; T y; T z; \");\r\n        }\r\n    }\r\n\r\n   /**\r\n    * Vector components\r\n    */\r\n    union\r\n    {\r\n        // Elements as static array\r\n        T[size] arrayof;\r\n\r\n        static if (size < 5)\r\n        {\r\n            /// Elements as x, y, z, w\r\n            struct { mixin(elements([\"x\", \"y\", \"z\", \"w\"])); }\r\n\r\n            /// Elements as r, g, b, a\r\n            struct { mixin(elements([\"r\", \"g\", \"b\", \"a\"])); }\r\n\r\n            /// Elements as s, t, p, q\r\n            struct { mixin(elements([\"s\", \"t\", \"p\", \"q\"])); }\r\n        }\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    {\r\n        const vec3 a = vec3(10.5f, 20.0f, 33.12345f);\r\n        const vec3 b = -a;\r\n        const vec3 c = +a - b;\r\n        const vec3 d = a * b / c;\r\n\r\n        assert(isAlmostZero(to!vec3(c.toString()) - c));\r\n\r\n        const vec3 v = vec3(10, 10, 10);\r\n        const vec3 vRes = (v / 10) * 2 - 1 + 5;\r\n        assert(isAlmostZero(vRes - vec3(6, 6, 6)));\r\n        assert(!vRes.isZero);\r\n\r\n        ivec2 ab = ivec2(5, 15);\r\n        ab += ivec2(20, 30);\r\n        ab *= 3;\r\n\r\n        assert(ab[0] == 75 && ab[1] == 135);\r\n\r\n        auto len = c.length();\r\n        auto lensqr = c.lengthsqr();\r\n        auto dist = distance(a, b);\r\n\r\n        auto xy = a[0..1];\r\n        auto n = a[];\r\n\r\n        vec3 v1 = vec3(2.0f, 0.0f, 1.0f);\r\n        ivec3 v2 = v1;\r\n        assert(ivec3(v1) == ivec3(2, 0, 1));\r\n\r\n        vec3 v3 = [0, 2, 3.5];\r\n        assert(v3 == vec3(0.0f, 2.0f, 3.5f));\r\n\r\n        ivec3 v4 = [7, 8, 3];\r\n        v4 %= 2;\r\n        assert(v4 == ivec3(1, 0, 1));\r\n    }\r\n\r\n    {\r\n        Vector3f a = Vector3f(1, 2, 3);\r\n        Vector2f b = Vector2f(a);\r\n        assert(b == Vector2f(1, 2));\r\n    }\r\n\r\n    {\r\n        Vector3f a = Vector3f([0, 1]);\r\n        assert(isNaN(a.z));\r\n    }\r\n\r\n    {\r\n        Vector3f a = Vector3f(0, 1, 2);\r\n        a += 1;\r\n        assert(a == Vector3f(1, 2, 3));\r\n        a *= 2;\r\n        assert(a == Vector3f(2, 4, 6));\r\n        a -= 1;\r\n        assert(a == Vector3f(1, 3, 5));\r\n        a /= 3;\r\n        assert(a.y == 1);\r\n    }\r\n\r\n    {\r\n        Vector3f a;\r\n        a[1] = 3;\r\n        assert(a.y == 3);\r\n\r\n        a[0..3] = 1;\r\n        assert(a == Vector3f(1, 1, 1));\r\n\r\n        a[] = 0;\r\n        assert(a == Vector3f(0, 0, 0));\r\n    }\r\n\r\n    {\r\n        Vector3i a = Vector3i(0, 0, 3);\r\n        a = a.normalized;\r\n        assert(a == Vector3i(0, 0, 1));\r\n        assert(a.length == 1);\r\n    }\r\n\r\n    {\r\n        Vector3f a = Vector3f(0, 0, 0);\r\n        assert(a.isZero);\r\n    }\r\n\r\n    {\r\n        Vector3f a = Vector3f(2, -3, 0);\r\n        a.clamp(-1, 1);\r\n        assert(a == Vector3f(1, -1, 0));\r\n    }\r\n\r\n    {\r\n        Vector3f a = Vector3f(2, 5, 7);\r\n        Vector4f b = Vector4f(a);\r\n        assert(b == Vector4f(2, 5, 7, float.nan));\r\n    }\r\n\r\n    {\r\n        Vector3f a = Vector3f([0, 1]);\r\n        assert(a == Vector3f(0, 1, float.nan));\r\n\r\n        Vector4f b = a.xyy;\r\n        assert(b == Vector4f(0, 1, 1, float.nan));\r\n    }\r\n\r\n    {\r\n        Vector3f a = Vector3f([0, 1]);\r\n        a.xz = Vector2f([6, 1]);\r\n        assert(a == Vector3f([6, 1, 1]));\r\n\r\n        a.zxy = Vector3f([1, 2, 3]);\r\n        assert(a == Vector3f([2, 3, 1]));\r\n    }\r\n\r\n    {\r\n        Vector3f a = Vector3f(1, 2, 3);\r\n        a = a + 1;\r\n        assert(a == Vector3f(2, 3, 4));\r\n        a = a * 2;\r\n        assert(a == Vector3f(4, 6, 8));\r\n        a = a - 2;\r\n        assert(a == Vector3f(2, 4, 6));\r\n        a = a / 2;\r\n        assert(a == Vector3f(1, 2, 3));\r\n\r\n        Vector3f b = Vector3f(3, 2, 1);\r\n        b += a;\r\n        assert(b == Vector3f(4, 4, 4));\r\n        b *= b;\r\n        assert(b == Vector3f(16, 16, 16));\r\n        b /= Vector3f(8, 4, 2);\r\n        assert(b == Vector3f(2, 4, 8));\r\n        b -= a;\r\n        assert(b == Vector3f(1, 2, 5));\r\n    }\r\n\r\n    {\r\n        Vector3f v = Vector3f(0, 0, 0);\r\n        v[0] = 5;\r\n        v[1] = 2;\r\n        assert(v == Vector3f(5, 2, 0));\r\n        v[1..3] = 12;\r\n        assert(v == Vector3f(5, 12, 12));\r\n        v[] = 0;\r\n        assert(v == Vector3f(0, 0, 0));\r\n    }\r\n\r\n    {\r\n        Vector4f a = Vector4f(2, 4, 6, 8);\r\n        Vector4f b = a.wxyz;\r\n        assert(b == Vector4f(8, 2, 4, 6));\r\n        float d = dot(a, b);\r\n        assert(d == 96.0f);\r\n    }\r\n\r\n    {\r\n        Vector2f a = Vector2f(1, 2);\r\n        Vector2f b = Vector2f(2, 1);\r\n        float d = dot(a, b);\r\n        assert(d == 4.0f);\r\n    }\r\n}\r\n\r\n/**\r\n * Dot product\r\n */\r\nT dot(T, int size) (Vector!(T,size) a, Vector!(T,size) b)\r\ndo\r\n{\r\n    static if (size == 1)\r\n    {\r\n        return a.x * b.x;\r\n    }\r\n    else\r\n    static if (size == 2)\r\n    {\r\n        return ((a.x * b.x) + (a.y * b.y));\r\n    }\r\n    else\r\n    static if (size == 3)\r\n    {\r\n        return ((a.x * b.x) + (a.y * b.y) + (a.z * b.z));\r\n    }\r\n    else\r\n    {\r\n        T d = 0;\r\n        //foreach (i; 0..size)\r\n        foreach(i; RangeTuple!(0, size))\r\n            d += a[i] * b[i];\r\n        return d;\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Vector3f v = Vector3f(1, 0, 0);\r\n    assert(dot(v, v) == 1);\r\n\r\n    Vector3f a = Vector3f(1, 2, 3, 4);\r\n    assert(dot(a, a) == 14);\r\n}\r\n\r\n/**\r\n * Cross product\r\n */\r\nVector!(T,size) cross(T, int size) (Vector!(T,size) a, Vector!(T,size) b) if (size == 3)\r\ndo\r\n{\r\n    return Vector!(T,size)\r\n    (\r\n        (a.y * b.z) - (a.z * b.y),\r\n        (a.z * b.x) - (a.x * b.z),\r\n        (a.x * b.y) - (a.y * b.x)\r\n    );\r\n}\r\n\r\n/**\r\n * Cross product for 4-vectors\r\n */\r\nVector!(T,size) cross(T, int size) (Vector!(T,size) a, Vector!(T,size) b, Vector!(T,size) c) if (size == 4)\r\ndo\r\n{\r\n    return Vector!(T,size)\r\n    (\r\n        (a.y * b.z * c.w) - (a.y * b.w * c.z)\r\n      + (a.z * b.w * c.y) - (a.z * b.y * c.w)\r\n      + (a.w * b.y * c.z) - (a.w * b.z * c.y),\r\n\r\n        (a.z * b.w * c.x) - (a.z * b.x * c.w)\r\n      + (a.w * b.x * c.z) - (a.w * b.z * c.x)\r\n      + (a.x * b.z * c.w) - (a.x * b.w * c.z),\r\n\r\n        (a.w * b.x * c.y) - (a.w * b.y * c.x)\r\n      + (a.x * b.y * c.w) - (a.x * b.w * c.y)\r\n      + (a.y * b.w * c.x) - (a.y * b.x * c.w),\r\n\r\n        (a.x * b.y * c.z) - (a.x * b.z * c.y)\r\n      + (a.y * b.z * c.x) - (a.y * b.x * c.z)\r\n      + (a.z * b.x * c.y) - (a.z * b.y * c.x)\r\n    );\r\n}\r\n\r\n/**\r\n * Tensor product\r\n */\r\nMatrix!(T,N) tensorProduct(T, size_t N) (Vector!(T,N) u, Vector!(T,N) v)\r\ndo\r\n{\r\n    Matrix!(T,N) res;\r\n    foreach(i; 0..N)\r\n    foreach(j; 0..N)\r\n    {\r\n        res[i, j] = u[i] * v[j];\r\n    }\r\n    return res;\r\n}\r\n\r\nalias outerProduct = tensorProduct;\r\n\r\n/**\r\n * Compute normal of a plane from three points\r\n */\r\nVector!(T,3) planeNormal(T) (Vector!(T,3) p1, Vector!(T,3) p2, Vector!(T,3) p3)\r\ndo\r\n{\r\n    Vector!(T,3) vec1 = Vector!(T,3)(p1 - p2);\r\n    Vector!(T,3) vec2 = Vector!(T,3)(p1 - p3);\r\n\r\n    Vector!(T,3) result = Vector!(T,3)(cross(vec1,vec2));\r\n    result.normalize();\r\n\r\n    return result;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    Vector3f n = planeNormal(\r\n        Vector3f(0, 0, 0),\r\n        Vector3f(0, 0, 1),\r\n        Vector3f(1, 0, 0)\r\n    );\r\n    assert(isAlmostZero(n - Vector3f(0, 1, 0)));\r\n}\r\n\r\n/**\r\n *\r\n */\r\nvoid rotateAroundAxis(T) (ref Vector!(T,3) V, Vector!(T,3) P, Vector!(T,3) D, T angle)\r\n{\r\n    T axx,axy,axz,ax1;\r\n    T ayx,ayy,ayz,ay1;\r\n    T azx,azy,azz,az1;\r\n\r\n    T u,v,w;\r\n    T u2,v2,w2;\r\n    T a,b,c;\r\n\r\n    T sa,ca;\r\n\r\n    sa = sin(angle);\r\n    ca = cos(angle);\r\n\r\n    u = D.x;\r\n    v = D.y;\r\n    w = D.z;\r\n\r\n    u2 = u * u;\r\n    v2 = v * v;\r\n    w2 = w * w;\r\n\r\n    a = P.x;\r\n    b = P.y;\r\n    c = P.z;\r\n\r\n    axx = u2+(v2+w2)*ca;\r\n    axy = u*v*(1-ca)-w*sa;\r\n    axz = u*w*(1-ca)+v*sa;\r\n    ax1 = a*(v2+w2)-u*(b*v+c*w)+(u*(b*v+c*w)-a*(v2+w2))*ca+(b*w-c*v)*sa;\r\n\r\n    ayx = u*v*(1-ca)+w*sa;\r\n    ayy = v2+(u2+w2)*ca;\r\n    ayz = v*w*(1-ca)-u*sa;\r\n    ay1 = b*(u2+w2)-v*(a*u+c*w)+(v*(a*u+c*w)-b*(u2+w2))*ca+(c*u-a*w)*sa;\r\n\r\n    azx = u*w*(1-ca)-v*sa;\r\n    azy = v*w*(1-ca)+u*sa;\r\n    azz = w2+(u2+v2)*ca;\r\n\r\n    az1 = c*(u2+v2)-w*(a*u+b*v)+(w*(a*u+b*v)-c*(u2+v2))*ca+(a*v-b*u)*sa;\r\n\r\n    Vector!(T,3) W;\r\n    W.x = axx * V.x + axy * V.y + axz * V.z + ax1;\r\n    W.y = ayx * V.x + ayy * V.y + ayz * V.z + ay1;\r\n    W.z = azx * V.x + azy * V.y + azz * V.z + az1;\r\n\r\n    V = W;\r\n}\r\n\r\n/**\r\n * Compute distance between two 2D points\r\n */\r\nT distance(T) (Vector!(T,2) a, Vector!(T,2) b)\r\ndo\r\n{\r\n    T dx = a.x - b.x;\r\n    T dy = a.y - b.y;\r\n    return sqrt((dx * dx) + (dy * dy));\r\n}\r\n\r\n/**\r\n * Compute squared distance between two 2D points\r\n */\r\nT distancesqr(T) (Vector!(T,2) a, Vector!(T,2) b)\r\ndo\r\n{\r\n    T dx = a.x - b.x;\r\n    T dy = a.y - b.y;\r\n    return ((dx * dx) + (dy * dy));\r\n}\r\n\r\n/**\r\n * Compute distance between two 3D points\r\n */\r\nT distance(T) (Vector!(T,3) a, Vector!(T,3) b)\r\ndo\r\n{\r\n    T dx = a.x - b.x;\r\n    T dy = a.y - b.y;\r\n    T dz = a.z - b.z;\r\n    return sqrt((dx * dx) + (dy * dy) + (dz * dz));\r\n}\r\n\r\n/**\r\n * Compute squared distance between two 3D points\r\n */\r\nT distancesqr(T) (Vector!(T,3) a, Vector!(T,3) b)\r\ndo\r\n{\r\n    T dx = a.x - b.x;\r\n    T dy = a.y - b.y;\r\n    T dz = a.z - b.z;\r\n    return ((dx * dx) + (dy * dy) + (dz * dz));\r\n}\r\n\r\n/**\r\n * Random unit length 2-vector\r\n */\r\nVector!(T,2) randomUnitVector2(T)()\r\n{\r\n    float azimuth = uniform(0.0, 1.0) * 2 * PI;\r\n    return Vector!(T,2)(cos(azimuth), sin(azimuth));\r\n}\r\n\r\n/**\r\n * Random unit length 3-vector\r\n */\r\nVector!(T,3) randomUnitVector3(T)()\r\n{\r\n    float z = (2 * uniform(0.0, 1.0)) - 1;\r\n    Vector!(T,2) planar = randomUnitVector2!(T)() * sqrt(1 - z * z);\r\n    return Vector!(T,3)(planar.x, planar.y, z);\r\n}\r\n\r\n/**\r\n * Spherical linear interpolation\r\n * (simple lerp is in dlib.math.interpolation)\r\n */\r\nVector!(T,3) slerp(T) (Vector!(T,3) a, Vector!(T,3) b, T t)\r\n{\r\n    T dp = dot(a, b);\r\n    dp = clamp(dp, -1.0, 1.0);\r\n    T theta = acos(dp) * t;\r\n    Vector!(T,3) relativeVec = b - a * dp;\r\n    relativeVec.normalize();\r\n    return ((a * cos(theta)) + (relativeVec * sin(theta)));\r\n}\r\n\r\n/**\r\n * Gradually decrease vector to zero length\r\n */\r\nVector!(T,3) vectorDecreaseToZero(T) (Vector!(T,3) vector, T step)\r\n{\r\n    foreach (ref component; vector.arrayof)\r\n    {\r\n        if (component > 0.0)\r\n            component -= step;\r\n        if (component < 0.0)\r\n            component += step;\r\n    }\r\n    return vector;\r\n}\r\n\r\n/**\r\n * Is all elements almost zero\r\n */\r\nbool isAlmostZero(Vector3f v)\r\n{\r\n    return (isConsiderZero(v.x) &&\r\n            isConsiderZero(v.y) &&\r\n            isConsiderZero(v.z));\r\n}\r\n\r\n/**\r\n *\r\n */\r\nVector!(T,3) reflect(T)(Vector!(T,3) I, Vector!(T,3) N)\r\n{\r\n    return I - N * dot(N, I) * 2.0;\r\n}\r\n\r\n/**\r\n *\r\n */\r\nVector!(T,3) refract(T)(Vector!(T,3) I, Vector!(T,3) N, T r)\r\n{\r\n    T d = 1.0 - r * r * (1.0 - dot(N, I) * dot(N, I));\r\n    if (d < 0.0)\r\n        return Vector!(T,3)(0.0, 0.0, 0.0);\r\n    return I * r - N * (r * dot(N, I) + sqrt(d));\r\n}\r\n\r\n/**\r\n *\r\n */\r\nVector!(T,3) faceforward(T)(Vector!(T,3) N, Vector!(T,3) I, Vector!(T,3) Nref)\r\n{\r\n    return dot(Nref, I) < 0.0 ? N : -N;\r\n}\r\n\r\n/**\r\n * Predefined vector types\r\n */\r\n/// Alias for 2-vector of ints\r\nalias Vector2i = Vector!(int, 2);\r\n/// Alias for 2-vector of uints\r\nalias Vector2u = Vector!(uint, 2);\r\n/// Alias for 2-vector of floats\r\nalias Vector2f = Vector!(float, 2);\r\n/// Alias for 2-vector of doubles\r\nalias Vector2d = Vector!(double, 2);\r\n\r\n/// Alias for 3-vector of ints\r\nalias Vector3i = Vector!(int, 3);\r\n/// Alias for 3-vector of uints\r\nalias Vector3u = Vector!(uint, 3);\r\n/// Alias for 3-vector of floats\r\nalias Vector3f = Vector!(float, 3);\r\n/// Alias for 2-vector of doubles\r\nalias Vector3d = Vector!(double, 3);\r\n\r\n/// Alias for 4-vector of ints\r\nalias Vector4i = Vector!(int, 4);\r\n/// Alias for 4-vector of uints\r\nalias Vector4u = Vector!(uint, 4);\r\n/// Alias for 4-vector of floats\r\nalias Vector4f = Vector!(float, 4);\r\n/// Alias for 4-vector of doubles\r\nalias Vector4d = Vector!(double, 4);\r\n\r\n/**\r\n * GLSL-like short aliases\r\n */\r\nalias ivec2 = Vector2i;\r\nalias uvec2 = Vector2u;\r\nalias vec2 = Vector2f;\r\nalias dvec2 = Vector2d;\r\n\r\nalias ivec3 = Vector3i;\r\nalias uvec3 = Vector3u;\r\nalias vec3 = Vector3f;\r\nalias dvec3 = Vector3d;\r\n\r\nalias ivec4 = Vector4i;\r\nalias uvec4 = Vector4u;\r\nalias vec4 = Vector4f;\r\nalias dvec4 = Vector4d;\r\n\r\n/**\r\n * Axis vectors\r\n */\r\nstatic struct AxisVector\r\n{\r\n    Vector3f x = Vector3f(1.0f, 0.0f, 0.0f);\r\n    Vector3f y = Vector3f(0.0f, 1.0f, 0.0f);\r\n    Vector3f z = Vector3f(0.0f, 0.0f, 1.0f);\r\n}\r\n\r\n/**\r\n * Vector factory function\r\n */\r\nauto vectorf(T...)(T t) if (t.length > 0)\r\n{\r\n    return Vector!(float, t.length)(t);\r\n}\r\n\r\n/**\r\n * L-value pseudovector for assignment purposes.\r\n *\r\n * Examples:\r\n * --------------------\r\n * float a, b, c;\r\n * lvector(a, b, c) = Vector3f(10, 4, 2);\r\n * --------------------\r\n */\r\nauto lvector(T...)(ref T x)\r\n{\r\n    struct Result(T, uint size)\r\n    {\r\n        T*[size] arrayof;\r\n\r\n        void opAssign(int size2)(Vector!(T,size2) v)\r\n        {\r\n            if (v.arrayof.length >= size)\r\n                foreach(i; 0..size)\r\n                    *arrayof[i] = v.arrayof[i];\r\n            else\r\n                foreach(i; 0..v.arrayof.length)\r\n                    *arrayof[i] = v.arrayof[i];\r\n        }\r\n    }\r\n\r\n    auto res = Result!(typeof(x[0]), x.length)();\r\n\r\n    foreach(i, ref v; x)\r\n        res.arrayof[i] = &v;\r\n\r\n    return res;\r\n}\r\n"
  },
  {
    "path": "dlib/memory/allocator.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Eugene Wissner\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\n\n/**\r\n * Abstract allocator interface\r\n *\n * Copyright: Eugene Wissner 2016-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Eugene Wissner\n */\nmodule dlib.memory.allocator;\n\nversion (unittest)\r\n{\r\n    import dlib.memory : defaultAllocator;\r\n}\n\n/**\n * Allocator interface.\n */\ninterface Allocator\n{\n    /**\n     * Allocates $(D_PARAM size) bytes of memory.\n     *\n     * Params:\n     *     size = Amount of memory to allocate.\n     *\n     * Returns: The pointer to the new allocated memory.\n     */\n    void[] allocate(size_t size);\n\n    /**\n     * Deallocates a memory block.\n     *\n     * Params:\n     *     p = A pointer to the memory block to be freed.\n     *\n     * Returns: Whether the deallocation was successful.\n     */\n    bool deallocate(void[] p);\n\n    /**\n     * Increases or decreases the size of a memory block.\n     *\n     * Params:\n     *     p    = A pointer to the memory block.\n     *     size = Size of the reallocated block.\n     *\n     * Returns: Whether the reallocation was successful.\n     */\n    bool reallocate(ref void[] p, size_t size);\n\n    /**\n     * Returns: The alignment offered.\n     */\n    @property immutable(uint) alignment() const @safe pure nothrow;\n}\n\n/**\r\n * Params:\r\n *     T         = Element type of the array being created.\r\n *     allocator = The allocator used for getting memory.\r\n *     array     = A reference to the array being changed.\r\n *     length    = New array length.\r\n *\r\n * Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could\r\n *          not be reallocated. In the latter\r\n */\r\nbool resizeArray(T)(Allocator allocator,\r\n                    ref T[] array,\r\n                    in size_t length)\r\n{\r\n    void[] buf = array;\r\n\r\n    if (!allocator.reallocate(buf, length * T.sizeof))\r\n    {\r\n        return false;\r\n    }\r\n    array = cast(T[]) buf;\r\n\r\n    return true;\r\n}\n\n///\r\nunittest\r\n{\r\n    int[] p;\r\n\r\n    defaultAllocator.resizeArray(p, 20);\r\n    assert(p.length == 20);\r\n\r\n    defaultAllocator.resizeArray(p, 30);\r\n    assert(p.length == 30);\r\n\r\n    defaultAllocator.resizeArray(p, 10);\r\n    assert(p.length == 10);\r\n\r\n    defaultAllocator.resizeArray(p, 0);\r\n    assert(p is null);\r\n}\n\nenum bool isFinalizable(T) =\n    is(T == class) ||\n    is(T == interface) ||\n    hasElaborateDestructor!T ||\n    isDynamicArray!T;\n"
  },
  {
    "path": "dlib/memory/arena.d",
    "content": "/*\nCopyright (c) 2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/** \n * General-purpose region-based memory allocator.\n *\n * Copyright: Timur Gafarov 2025.\n * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.memory.arena;\n\nimport std.traits;\nimport std.conv;\nimport std.meta;\nimport std.string;\nimport std.uni: isWhite;\nimport core.exception: onOutOfMemoryError;\nimport core.stdc.stdio;\n\nimport dlib.core.memory;\nimport dlib.core.ownership;\nimport dlib.container.array;\n\n/// Represents a single memory block within the `Arena`.\nstruct ArenaBuffer\n{\n    ubyte[] data;\n    size_t offset;\n}\n\n/// Represents an allocation state of the `ArenaBuffer`.\nstruct AllocationMark\n{\n    ArenaBuffer* buffer;\n    size_t offset;\n    size_t size;\n}\n\n/**\n * Arena is an owned allocator that pre-allocates memory in large blocks\n * (buffers) and distributes it in chunks on demand.\n * It never frees its buffers; they are freed at once on Arena destruction.\n * The whole Arena can be reset and reused without freeing.\n * This allows for efficient memory management for many small objects,\n * such as strings.\n *\n * Arena supports:\n * - allocating class instances, structures, arrays and strings\n * - automatic memory alignment\n * - reusing already allocated memory\n * - creating marks and rolling back to them\n */\nclass Arena: Owner\n{\n    protected:\n    \n    Array!ArenaBuffer buffers;\n    size_t currentBufferIndex;\n    ArenaBuffer* lastBuffer;\n    size_t lastAllocationSize;\n    \n    public:\n    \n    Array!Object objects;\n    \n    /**\n     * Constructor.\n     *\n     * Params:\n     *   bufferSize = Initial buffer size.\n     *   owner = Owner object.\n     */\n    this(size_t bufferSize, Owner owner = null)\n    {\n        super(owner);\n        addBuffer(bufferSize);\n        currentBufferIndex = 0;\n        lastAllocationSize = 0;\n        lastBuffer = null;\n        objects.reserve(1024);\n    }\n    \n    ~this()\n    {\n        release();\n    }\n    \n    /// Resets arena for reuse.\n    void reset()\n    {\n        if (objects.length)\n        {\n            // Call destructors\n            foreach_reverse(obj; objects)\n            {\n                destroy(obj);\n            }\n            \n            // Don't free the register, just reset its position\n            objects.removeBack(cast(uint)objects.length);\n        }\n        \n        foreach (ref buf; buffers)\n            buf.offset = 0;\n        currentBufferIndex = 0;\n        lastAllocationSize = 0;\n        lastBuffer = null;\n    }\n    \n    /// Releases all allocated buffers.\n    void release()\n    {\n        // Call destructors and free the register\n        foreach_reverse(Object obj; this.objects)\n        {\n            destroy(obj);\n        }\n        objects.free();\n        \n        foreach(buf; buffers)\n            Delete(buf.data);\n        buffers.free();\n        \n        currentBufferIndex = 0;\n        lastAllocationSize = 0;\n        lastBuffer = null;\n    }\n    \n    /**\n     * Aligns the specified offset with the given alignment.\n     *\n     * Params:\n     *   offset - The original offset.\n     *   alignment - The desired alignment (power of two).\n     *\n     * Returns:\n     *   The new offset, aligned with alignment.\n     */\n    size_t alignup(size_t offset, size_t alignment)\n    {\n        return (offset + alignment - 1) & ~(alignment - 1);\n    }\n    \n    /**\n     * Adds a new memory buffer.\n     *\n     * Params:\n     *   size - The buffer size in bytes.\n     *\n     * Returns:\n     *   A pointer to the created buffer.\n     */\n    protected ArenaBuffer* addBuffer(size_t size)\n    {\n        if (size == 0)\n            size = 1024;\n        ArenaBuffer buf = {\n            data: New!(ubyte[])(size),\n            offset: 0\n        };\n        buffers.append(buf);\n        return &buffers.data[buffers.length - 1];\n    }\n    \n    /**\n     * Allocates a chunk memory in the arena.\n     *\n     * Params:\n     *   size - Chunk size in bytes.\n     *   alignment = 1 - Desired alignment.\n     *\n     * Returns:\n     *   Slice of allocated memory.\n     */\n    ubyte[] allocate(size_t size, size_t alignment = 1)\n    {\n        // Fast path: allocate in the current buffer, if possible\n        ArenaBuffer* currentBuffer = &buffers.data[currentBufferIndex];\n        size_t alignedOffset = alignup(currentBuffer.offset, alignment);\n        if (alignedOffset + size <= currentBuffer.data.length)\n        {\n            ubyte[] res = currentBuffer.data[alignedOffset..alignedOffset+size];\n            currentBuffer.offset = alignedOffset + size;\n            lastBuffer = currentBuffer;\n            lastAllocationSize = size;\n            return res;\n        }\n        \n        // Try to find an existing buffer with enough space\n        foreach (i, ref buf; buffers.data)\n        {\n            alignedOffset = alignup(buf.offset, alignment);\n            if (alignedOffset + size <= buf.data.length)\n            {\n                currentBufferIndex = i;\n                ubyte[] res = buf.data[alignedOffset..alignedOffset+size];\n                buf.offset = alignedOffset + size;\n                lastBuffer = &buffers.data[i];\n                lastAllocationSize = size;\n                return res;\n            }\n        }\n        \n        // No suitable buffer found, allocate a new one\n        currentBuffer = addBuffer(size);\n        currentBufferIndex = buffers.length - 1;\n        ubyte[] res = currentBuffer.data[0..size];\n        currentBuffer.offset = size;\n        lastBuffer = currentBuffer;\n        lastAllocationSize = size;\n        return res;\n    }\n\n    /**\n     * Shrinks the last allocation to a new size.\n     * Only allowed if newSize <= lastAllocationSize.\n     *\n     * Params:\n     *   newSize - New allocation size.\n     */\n    void shrinkLastAllocation(size_t newSize)\n    {\n        if (newSize <= lastAllocationSize)\n        {\n            // Move the buffer offset back by the difference\n            lastBuffer.offset -= (lastAllocationSize - newSize);\n            lastAllocationSize = newSize;\n        }\n    }\n    \n    /**\n     * Creates a mark for the last allocation.\n     */\n    AllocationMark mark()\n    {\n        return AllocationMark(lastBuffer, lastBuffer ? lastBuffer.offset : 0, lastAllocationSize);\n    }\n    \n    /**\n     * Rolls back to the specified mark, reclaiming a memory allocated in the buffer after the mark.\n     * Use with caution: all objects created after the mark are invalidated.\n     *\n     * Params:\n     *   mark - Allocation mark to roll back to.\n     */\n    void rollback(AllocationMark mark)\n    {\n        if (mark.buffer !is null)\n        {\n            lastBuffer = mark.buffer;\n            lastBuffer.offset = mark.offset;\n        }\n    }\n    \n    /**\n     * Creates an object of class T in the arena.\n     *\n     * Params:\n     *   args - Arguments to the T constructor.\n     *\n     * Returns:\n     *   The created object of type T.\n     */\n    T create(T, A...) (A args) if (is(T == class))\n    {\n        enum size = __traits(classInstanceSize, T);\n        ubyte* p = allocate(size + psize, T.alignof).ptr;\n        if (!p)\n            onOutOfMemoryError();\n        auto memory = p[psize..psize+size];\n        *cast(size_t*)p = size;\n        T res = emplace!(T, A)(memory, args);\n        objects.append(res);\n        return res;\n    }\n    \n    /**\n     * Creates an instance of structure T in the arena.\n     *\n     * Params:\n     *   args - structure constructor arguments.\n     *\n     * Returns:\n     *   A pointer to the created structure instance.\n     */\n    T* create(T, A...) (A args) if (is(T == struct))\n    {\n        enum size = T.sizeof;\n        ubyte* p = allocate(size + psize, T.alignof).ptr;\n        if (!p)\n            onOutOfMemoryError();\n        auto memory = p[psize..psize+size];\n        *cast(size_t*)p = size;\n        return emplace!(T, A)(memory, args);\n    }\n    \n    /**\n     * Creates an array in the arena.\n     *\n     * Params:\n     *   length - The length of the array.\n     *\n     * Returns:\n     *   A new array of type T.\n     */\n    T create(T)(size_t length) if (isArray!T)\n    {\n        alias AT = ForeachType!T;\n        size_t size = length * AT.sizeof;\n        ubyte[] mem = allocate(size + psize, AT.alignof);\n        if (!mem.length)\n            onOutOfMemoryError();\n        T arr = cast(T)mem[psize..psize+size];\n        foreach(ref v; arr)\n            v = v.init;\n        *cast(size_t*)mem = size;\n        return arr;\n    }\n    \n    /**\n     * Allocates space for a null-terminated array of characters of the specified length.\n     *\n     * Params:\n     *   len - The length of the string (excluding the null-terminator).\n     *\n     * Returns:\n     *   An array of characters.\n     */\n    char[] allocateString(size_t len)\n    {\n        char[] space = cast(char[])allocate(len + 1);\n        space[len] = 0; // zero-terminate\n        return space[0..len];\n    }\n    \n    /**\n     * Copies a string to the arena, appending a null-terminator for C compatibility.\n     *\n     * Params:\n     *   str - The original string.\n     *\n     * Returns:\n     *   The string stored in the arena.\n     */\n    string store(string str)\n    {\n        char[] space = allocateString(str.length);\n        space[] = str[];\n        return cast(string)space;\n    }\n}\n\nT[] cat(T)(Arena arena, T[] a, T[] b)\n{\n    T[] arr = arena.create!(T[])(a.length + b.length);\n    arr[0..a.length] = a[];\n    arr[a.length..$] = b[];\n    return arr;\n}\n\nT[] cat(T)(Arena arena, T[] a, T b)\n{\n    T[] arr = arena.create!(T[])(a.length + 1);\n    arr[0..a.length] = a[];\n    arr[a.length] = b;\n    return arr;\n}\n\nT[] cat(T)(Arena arena, T a, T[] b)\n{\n    T[] arr = arena.create!(T[])(b.length + 1);\n    arr[1..b.length] = b[];\n    arr[0] = a;\n    return arr;\n}\n\n/**\n * Concatenates two strings and stores the result in the arena.\n *\n * Params:\n *   arena - The memory arena.\n *   a - First string.\n *   b - Second string.\n *\n * Returns:\n *   The concatenated string.\n */\nstring cat(Arena arena, string a, string b)\n{\n    char[] space = arena.allocateString(a.length + b.length);\n    space[0..a.length] = a[];\n    space[a.length..$] = b[];\n    return cast(string)space;\n}\n\n/**\n * Concatenates any number of strings and stores the result in the arena.\n *\n * Params:\n *   arena - The memory arena.\n *   args - Strings to concatenate.\n *\n * Returns:\n *   The concatenated string.\n */\nstring cat(Args...)(Arena arena, Args args)\n    if (Args.length > 0 && allSatisfy!(isSomeString, Args))\n{\n    size_t totalLen = 0;\n    foreach (s; args)\n        totalLen += s.length;\n    char[] space = arena.allocateString(totalLen);\n    size_t pos = 0;\n    foreach (s; args)\n    {\n        space[pos .. pos + s.length] = s[];\n        pos += s.length;\n    }\n    return cast(string)space;\n}\n\n/**\n * Formats a string using the given format string and arguments, storing the result in the arena.\n *\n * Params:\n *   arena - The memory arena.\n *   fmt - The format string.\n *   args - Arguments to format.\n *\n * Returns:\n *   The formatted string stored in the arena.\n */\nstring format(T...)(Arena arena, string fmt, T args)\n{\n    // Estimate the required size\n    size_t size = 256;\n    while (true)\n    {\n        char[] space = arena.allocateString(size);\n        auto written = snprintf(space.ptr, space.length, fmt.ptr, args);\n        if (written < 0)\n            onOutOfMemoryError();\n        if (cast(size_t)written < space.length)\n        {\n            // Successfully formatted, shrink to actual size, including null-terminator\n            arena.shrinkLastAllocation(cast(size_t)written + 1);\n\n            // Return the formatted string\n            return cast(string)space[0..cast(size_t)written];\n        }\n        else\n        {\n            // Not enough space, try again with a larger buffer\n            size = cast(size_t)written + 1;\n\n            // Rollback the buffer\n            arena.shrinkLastAllocation(0);\n        }\n    }\n    \n    // Should never get here\n    return \"\";\n}\n\n/**\n * Splits a string by the specified delimiter and stores the parts in the arena.\n *\n * Params:\n *   arena - The memory arena.\n *   str - The original string.\n *   delimiter - The delimiter character.\n *\n * Returns:\n *   An array of strings.\n */\nstring[] split(Arena arena, string str, char delimiter)\n{\n    size_t count = 1;\n    foreach (c; str)\n        if (c == delimiter)\n            count++;\n\n    auto result = arena.create!(string[])(count);\n\n    size_t index = 0;\n    size_t start = 0;\n    size_t numChars = 0;\n    for (size_t i = 0; i < str.length; i++)\n    {\n        if (str[i] == delimiter && numChars > 0)\n        {\n            string part = arena.store(str[start..start+numChars]);\n            result[index++] = part;\n            start = i + 1;\n            numChars = 0;\n        }\n        else\n            numChars++;\n    }\n    \n    if (numChars > 0)\n    {\n        string part = arena.store(str[start..start+numChars]);\n        result[index++] = part;\n    }\n\n    return result;\n}\n\n/**\n * Joins an array of strings with the specified separator and stores the result in the arena.\n *\n * Params:\n *   arena - The memory arena.\n *   parts - The array of strings to join.\n *   sep - The separator string.\n *\n * Returns:\n *   The joined string.\n */\nstring join(Arena arena, string[] parts, string sep)\n{\n    if (parts.length == 0) return \"\";\n    size_t totalLen = (parts.length - 1) * sep.length;\n    foreach (p; parts)\n        totalLen += p.length;\n    char[] buf = arena.allocateString(totalLen);\n    size_t pos = 0;\n    foreach (i, p; parts)\n    {\n        if (i > 0)\n        {\n            buf[pos..pos+sep.length] = sep[];\n            pos += sep.length;\n        }\n        buf[pos..pos+p.length] = p[];\n        pos += p.length;\n    }\n    return cast(string)buf;\n}\n\n/**\n * Replaces all occurrences of a substring with another substring in the given string,\n * storing the result in the arena.\n *\n * Params:\n *   arena - The memory arena.\n *   str - The original string.\n *   strFrom - The substring to replace.\n *   strTo - The replacement substring.\n *\n * Returns:\n *   The modified string with replacements.\n */\nstring replace(Arena arena, string str, string strFrom, string strTo)\n{\n    if (strFrom.length == 0) return arena.store(str);\n    size_t count = 0, pos = 0;\n    while ((pos = str.indexOf(strFrom, pos)) != -1)\n    {\n        ++count;\n        pos += strFrom.length;\n    }\n    if (count == 0)\n        return arena.store(str);\n\n    size_t newLen = str.length + count * (strTo.length - strFrom.length);\n    char[] buf = arena.allocateString(newLen);\n    size_t src = 0, dst = 0;\n    while (src < str.length)\n    {\n        auto idx = str.indexOf(strFrom, src);\n        if (idx == -1)\n        {\n            buf[dst..dst+str.length-src] = str[src..$];\n            break;\n        }\n        buf[dst..dst+idx-src] = str[src..idx];\n        dst += idx - src;\n        buf[dst..dst+strTo.length] = strTo[];\n        dst += strTo.length;\n        src = idx + strFrom.length;\n    }\n    return cast(string)buf;\n}\n\n/**\n * Trims whitespace from both ends of the string and stores the result in the arena.\n *\n * Params:\n *   arena - The memory arena.\n *   s - The original string.\n *\n * Returns:\n *   The trimmed string.\n */\nstring trim(Arena arena, string s)\n{\n    size_t start = 0, end = s.length;\n    while (start < end && isWhite(s[start])) ++start;\n    while (end > start && isWhite(s[end-1])) --end;\n    return arena.store(s[start .. end]);\n}\n\n/**\n * Duplicates an array into the arena.\n *\n * Params:\n *   arena - The memory arena.\n *   arr - The original array.\n *\n * Returns:\n *   A copy of the array stored in the arena.\n */\nT[] dup(T)(Arena arena, const(T)[] arr)\n{\n    auto copy = arena.create!(T[])(arr.length);\n    copy[] = arr[];\n    return copy;\n}\n\n/**\n * Duplicates an array into the arena as an immutable array.\n *\n * Params:\n *   arena - The memory arena.\n *   arr - The original array.\n *\n * Returns:\n *   An immutable copy of the array stored in the arena.\n */\nimmutable(T)[] idup(T)(Arena arena, const(T)[] arr)\n{\n    return cast(immutable(T)[])dup(arena, arr);\n}\n"
  },
  {
    "path": "dlib/memory/gcallocator.d",
    "content": "/*\r\nCopyright (c) 2017-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\n\n/**\n * Allocator based on D's built-in garbage collector\n *\n * Copyright: Timur Gafarov 2017-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.memory.gcallocator;\n\nimport core.exception;\nimport core.memory;\nimport std.algorithm.comparison;\nimport dlib.memory.allocator;\n\n/**\n * Allocator based on D's built-in garbage collector\n */\nclass GCallocator: Allocator\n{\n    void[] allocate(size_t size)\n    {\n        return GC.malloc(size)[0..size];\n    }\n\n    bool deallocate(void[] p)\n    {\n        GC.free(p.ptr);\n        return true;\n    }\n\n    bool reallocate(ref void[] p, size_t size)\n    {\n        GC.realloc(p.ptr, size);\n        return true;\n    }\n\n    @property immutable(uint) alignment() const\n    {\n        return cast(uint) max(double.alignof, real.alignof);\n    }\n\n    static @property GCallocator instance() nothrow\n    {\n        if (instance_ is null)\n        {\n            immutable size = __traits(classInstanceSize, GCallocator);\n            void* p = GC.malloc(size);\n\n            if (p is null)\n            {\n                onOutOfMemoryError();\n            }\n            p[0..size] = typeid(GCallocator).initializer[];\n            instance_ = cast(GCallocator)p[0..size].ptr;\n\n        }\n        return instance_;\n    }\n\n    private static __gshared GCallocator instance_;\n}\n"
  },
  {
    "path": "dlib/memory/mallocator.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Eugene Wissner\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\n\n/**\n * Allocator based on malloc/realloc/free\n *\n * Copyright: Eugene Wissner 2016-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Eugene Wissner\n */\nmodule dlib.memory.mallocator;\n\nimport dlib.memory.allocator;\nimport core.exception;\nimport core.stdc.stdlib;\nimport std.algorithm.comparison;\n\n/**\n * Wrapper for malloc/realloc/free from the C standard library.\n */\nclass Mallocator: Allocator\n{\n    /**\n     * Allocates $(D_PARAM size) bytes of memory.\n     *\n     * Params:\n     *     size = Amount of memory to allocate.\n     *\n     * Returns: The pointer to the new allocated memory.\n     */\n    void[] allocate(size_t size)\n    {\n        if (!size)\n        {\n            return null;\n        }\n        auto p = malloc(size);\n\n        if (!p)\n        {\n            onOutOfMemoryError();\n        }\n        return p[0..size];\n    }\n\n    ///\n    unittest\n    {\n        auto p = Mallocator.instance.allocate(20);\n\n        assert(p.length == 20);\n\n        Mallocator.instance.deallocate(p);\n    }\n\n    /**\n     * Deallocates a memory block.\n     *\n     * Params:\n     *     p = A pointer to the memory block to be freed.\n     *\n     * Returns: Whether the deallocation was successful.\n     */\n    bool deallocate(void[] p)\n    {\n        if (p !is null)\n        {\n            free(p.ptr);\n        }\n        return true;\n    }\n\n    ///\n    unittest\n    {\n        void[] p;\n        assert(Mallocator.instance.deallocate(p));\n\n        p = Mallocator.instance.allocate(10);\n        assert(Mallocator.instance.deallocate(p));\n    }\n\n    /**\n     * Increases or decreases the size of a memory block.\n     *\n     * Params:\n     *     p    = A pointer to the memory block.\n     *     size = Size of the reallocated block.\n     *\n     * Returns: Whether the reallocation was successful.\n     */\n    bool reallocate(ref void[] p, size_t size)\n    {\n        if (!size)\n        {\n            deallocate(p);\n            p = null;\n            return true;\n        }\n        else if (p is null)\n        {\n            p = allocate(size);\n            return true;\n        }\n        auto r = realloc(p.ptr, size);\n\n        if (!r)\n        {\n            onOutOfMemoryError();\n        }\n        p = r[0..size];\n\n        return true;\n    }\n\n    ///\n    unittest\n    {\n        void[] p;\n\n        Mallocator.instance.reallocate(p, 20);\n        assert(p.length == 20);\n\n        Mallocator.instance.reallocate(p, 30);\n        assert(p.length == 30);\n\n        Mallocator.instance.reallocate(p, 10);\n        assert(p.length == 10);\n\n        Mallocator.instance.reallocate(p, 0);\n        assert(p is null);\n    }\n\n    /**\n     * Returns: The alignment offered.\n     */\n    @property immutable(uint) alignment() const\n    {\n        return cast(uint) max(double.alignof, real.alignof);\n    }\n\n    /**\n     * Static allocator instance and initializer.\n     *\n     * Returns: The global $(D_PSYMBOL Allocator) instance.\n     */\n    static @property Mallocator instance() @nogc nothrow\n    {\n        if (instance_ is null)\n        {\n            immutable size = __traits(classInstanceSize, Mallocator);\n            void* p = malloc(size);\n\n            if (p is null)\n            {\n                onOutOfMemoryError();\n            }\n            p[0..size] = typeid(Mallocator).initializer[];\n            instance_ = cast(Mallocator) p[0..size].ptr;\n        }\n        return instance_;\n    }\n\n    ///\n    @nogc nothrow unittest\n    {\n        assert(instance is instance);\n    }\n\n    private static __gshared Mallocator instance_;\n}\n"
  },
  {
    "path": "dlib/memory/mmappool.d",
    "content": "/*\nCopyright (c) 2016-2025 Eugene Wissner\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\r\n * Fast block-based allocator\r\n *\n * Copyright: Eugene Wissner 2016-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Eugene Wissner\n */\nmodule dlib.memory.mmappool;\n\nimport dlib.memory.allocator;\r\nimport core.atomic;\r\nimport core.exception;\r\n\r\nversion (Posix)\r\n{\r\n    import core.stdc.errno;\r\n    import core.sys.posix.sys.mman;\r\n    import core.sys.posix.unistd;\r\n}\r\nelse version (Windows)\r\n{\r\n    import core.sys.windows.winbase;\r\n    import core.sys.windows.windows;\r\n}\r\n\r\n/**\r\n * This allocator allocates memory in regions (multiple of 4 KB for example).\r\n * Each region is then splitted in blocks. So it doesn't request the memory\r\n * from the operating system on each call, but only if there are no large\r\n * enough free blocks in the available regions.\r\n * Deallocation works in the same way. Deallocation doesn't immediately\r\n * gives the memory back to the operating system, but marks the appropriate\r\n * block as free and only if all blocks in the region are free, the complete\r\n * region is deallocated.\r\n *\r\n * ----------------------------------------------------------------------------\r\n * |      |     |         |     |            ||      |     |                  |\r\n * |      |prev <-----------    |            ||      |     |                  |\r\n * |  R   |  B  |         |  B  |            ||   R  |  B  |                  |\r\n * |  E   |  L  |         |  L  |           next  E  |  L  |                  |\r\n * |  G   |  O  |  DATA   |  O  |   FREE    --->  G  |  O  |       DATA       |\r\n * |  I   |  C  |         |  C  |           <---  I  |  C  |                  |\r\n * |  O   |  K  |         |  K  |           prev  O  |  K  |                  |\r\n * |  N   |    -----------> next|            ||   N  |     |                  |\r\n * |      |     |         |     |            ||      |     |                  |\r\n * ----------------------------------------------------------------------------\r\n *\r\n * TODO:\r\n *     $(UL\r\n *         $(LI Thread safety (core.atomic.cas))\r\n *         $(LI If two neighbour blocks are free, they can be merged)\r\n *         $(LI Reallocation shoud check if there is enough free space in the\r\n *              next block instead of always moving the memory)\r\n *         $(LI Make 64 KB regions mininmal region size on Linux)\r\n *     )\r\n */\r\nclass MmapPool: Allocator\r\n{\r\n    static initialize()\r\n    {\r\n        version (Posix)\r\n        {\r\n            pageSize = sysconf(_SC_PAGE_SIZE);\r\n        }\r\n        else version (Windows)\r\n        {\r\n            SYSTEM_INFO si;\r\n            GetSystemInfo(&si);\r\n            pageSize = si.dwPageSize;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Allocates $(D_PARAM size) bytes of memory.\r\n     *\r\n     * Params:\r\n     *     size = Amount of memory to allocate.\r\n     *\r\n     * Returns: The pointer to the new allocated memory.\r\n     */\r\n    void[] allocate(size_t size) @nogc @trusted nothrow\r\n    {\r\n        if (!size)\r\n        {\r\n            return null;\r\n        }\r\n        immutable dataSize = addAlignment(size);\r\n\r\n        void* data = findBlock(dataSize);\r\n        if (data is null)\r\n        {\r\n            data = initializeRegion(dataSize);\r\n        }\r\n\r\n        return data is null ? null : data[0..size];\r\n    }\r\n\r\n    ///\r\n    @nogc @safe nothrow unittest\r\n    {\r\n        auto p = MmapPool.instance.allocate(20);\r\n\r\n        assert(p);\r\n\r\n        MmapPool.instance.deallocate(p);\r\n    }\r\n\r\n    /**\r\n     * Search for a block large enough to keep $(D_PARAM size) and split it\r\n     * into two blocks if the block is too large.\r\n     *\r\n     * Params:\r\n     *     size = Minimum size the block should have.\r\n     *\r\n     * Returns: Data the block points to or $(D_KEYWORD null).\r\n     */\r\n    private void* findBlock(size_t size) @nogc nothrow\r\n    {\r\n        Block block1;\r\n        RegionLoop: for (auto r = head; r !is null; r = r.next)\r\n        {\r\n            block1 = cast(Block) (cast(void*) r + regionEntrySize);\r\n            do\r\n            {\r\n                if (block1.free && block1.size >= size)\r\n                {\r\n                    break RegionLoop;\r\n                }\r\n            }\r\n            while ((block1 = block1.next) !is null);\r\n        }\r\n        if (block1 is null)\r\n        {\r\n            return null;\r\n        }\r\n        else if (block1.size >= size + alignment + blockEntrySize)\r\n        { // Split the block if needed\r\n            Block block2 = cast(Block) (cast(void*) block1 + blockEntrySize + size);\r\n            block2.prev = block1;\r\n            if (block1.next is null)\r\n            {\r\n                block2.next = null;\r\n            }\r\n            else\r\n            {\r\n                block2.next = block1.next.next;\r\n            }\r\n            block1.next = block2;\r\n\r\n            block1.free = false;\r\n            block2.free = true;\r\n\r\n            block2.size = block1.size - blockEntrySize - size;\r\n            block1.size = size;\r\n\r\n            block2.region = block1.region;\r\n            //atomicOp!\"+=\"(block1.region.blocks, 1); // atomicOp works only with shared data\n            ++block1.region.blocks;\r\n        }\r\n        else\r\n        {\r\n            block1.free = false;\r\n            //atomicOp!\"+=\"(block1.region.blocks, 1); // atomicOp works only with shared data\n            ++block1.region.blocks;\r\n        }\r\n        return cast(void*) block1 + blockEntrySize;\r\n    }\r\n\r\n    /**\r\n     * Deallocates a memory block.\r\n     *\r\n     * Params:\r\n     *     p = A pointer to the memory block to be freed.\r\n     *\r\n     * Returns: Whether the deallocation was successful.\r\n     */\r\n    bool deallocate(void[] p) @nogc @trusted nothrow\r\n    {\r\n        if (p is null)\r\n        {\r\n            return true;\r\n        }\r\n\r\n        Block block = cast(Block) (p.ptr - blockEntrySize);\r\n        if (block.region.blocks <= 1)\r\n        {\r\n            if (block.region.prev !is null)\r\n            {\r\n                block.region.prev.next = block.region.next;\r\n            }\r\n            else // Replace the list head. It is being deallocated\r\n            {\r\n                head = block.region.next;\r\n            }\r\n            if (block.region.next !is null)\r\n            {\r\n                block.region.next.prev = block.region.prev;\r\n            }\r\n            version (Posix)\r\n            {\r\n                return munmap(cast(void*) block.region, block.region.size) == 0;\r\n            }\r\n            version (Windows)\r\n            {\r\n                return VirtualFree(cast(void*) block.region, 0, MEM_RELEASE) == 0;\r\n            }\r\n        }\r\n        else\r\n        {\r\n            block.free = true;\r\n            //atomicOp!\"-=\"(block.region.blocks, 1); // atomicOp works only with shared data\n            --block.region.blocks;\r\n            return true;\r\n        }\r\n    }\r\n\r\n    ///\r\n    @nogc @safe nothrow unittest\r\n    {\r\n        auto p = MmapPool.instance.allocate(20);\r\n\r\n        assert(MmapPool.instance.deallocate(p));\r\n    }\r\n\r\n    /**\r\n     * Increases or decreases the size of a memory block.\r\n     *\r\n     * Params:\r\n     *     p    = A pointer to the memory block.\r\n     *     size = Size of the reallocated block.\r\n     *\r\n     * Returns: Whether the reallocation was successful.\r\n     */\r\n    bool reallocate(ref void[] p, size_t size) @nogc @trusted nothrow\r\n    {\r\n        void[] reallocP;\r\n\r\n        if (size == p.length)\r\n        {\r\n            return true;\r\n        }\r\n        else if (size > 0)\r\n        {\r\n            reallocP = allocate(size);\r\n            if (reallocP is null)\r\n            {\r\n                return false;\r\n            }\r\n        }\r\n\r\n        if (p !is null)\r\n        {\r\n            if (size > p.length)\r\n            {\r\n                reallocP[0..p.length] = p[0..$];\r\n            }\r\n            else if (size > 0)\r\n            {\r\n                reallocP[0..size] = p[0..size];\r\n            }\r\n            deallocate(p);\r\n        }\r\n        p = reallocP;\r\n\r\n        return true;\r\n    }\r\n\r\n    ///\r\n    @nogc nothrow unittest\r\n    {\r\n        void[] p;\r\n        MmapPool.instance.reallocate(p, 10 * int.sizeof);\r\n        (cast(int[]) p)[7] = 123;\r\n\r\n        assert(p.length == 40);\r\n\r\n        MmapPool.instance.reallocate(p, 8 * int.sizeof);\r\n\r\n        assert(p.length == 32);\r\n        assert((cast(int[]) p)[7] == 123);\r\n\r\n        MmapPool.instance.reallocate(p, 20 * int.sizeof);\r\n        (cast(int[]) p)[15] = 8;\r\n\r\n        assert(p.length == 80);\r\n        assert((cast(int[]) p)[15] == 8);\r\n        assert((cast(int[]) p)[7] == 123);\r\n\r\n        MmapPool.instance.reallocate(p, 8 * int.sizeof);\r\n\r\n        assert(p.length == 32);\r\n        assert((cast(int[]) p)[7] == 123);\r\n\r\n        MmapPool.instance.deallocate(p);\r\n    }\r\n\r\n    /**\r\n     * Static allocator instance and initializer.\r\n     *\r\n     * Returns: Global $(D_PSYMBOL MmapPool) instance.\r\n     */\r\n    static @property MmapPool instance() @nogc @trusted nothrow\r\n    {\r\n        if (instance_ is null)\r\n        {\n            initialize();\n\r\n            immutable instanceSize = addAlignment(__traits(classInstanceSize, MmapPool));\r\n\r\n            Region head; // Will become soon our region list head\r\n            void* data = initializeRegion(instanceSize, head);\r\n            if (data !is null)\r\n            {\r\n                data[0..instanceSize] = typeid(MmapPool).initializer[];\r\n                instance_ = cast(MmapPool) data;\r\n                instance_.head = head;\r\n            }\r\n        }\r\n        return instance_;\r\n    }\r\n\r\n    ///\r\n    @nogc @safe nothrow unittest\r\n    {\r\n        assert(instance is instance);\r\n    }\r\n\r\n    /**\r\n     * Initializes a region for one element.\r\n     *\r\n     * Params:\r\n     *     size = Aligned size of the first data block in the region.\r\n     *     head = Region list head.\r\n     *\r\n     * Returns: A pointer to the data.\r\n     */\r\n    pragma(inline)\r\n    private static void* initializeRegion(size_t size,\r\n                                          ref Region head) @nogc nothrow\r\n    {\r\n        immutable regionSize = calculateRegionSize(size);\r\n\r\n        version (Posix)\r\n        {\r\n            void* p = mmap(null,\r\n                           regionSize,\r\n                           PROT_READ | PROT_WRITE,\r\n                           MAP_PRIVATE | MAP_ANON,\r\n                           -1,\r\n                           0);\r\n            if (p is MAP_FAILED)\r\n            {\r\n                if (errno == ENOMEM)\r\n                {\r\n                    onOutOfMemoryError();\r\n                }\r\n                return null;\r\n            }\r\n        }\r\n        else version (Windows)\r\n        {\r\n            void* p = VirtualAlloc(null,\r\n                                   regionSize,\r\n                                   MEM_COMMIT,\r\n                                   PAGE_READWRITE);\r\n            if (p is null)\r\n            {\r\n                if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY)\r\n                {\r\n                    onOutOfMemoryError();\r\n                }\r\n                return null;\r\n            }\r\n        }\r\n\r\n        Region region = cast(Region) p;\r\n        region.blocks = 1;\r\n        region.size = regionSize;\r\n\r\n        // Set the pointer to the head of the region list\r\n        if (head !is null)\r\n        {\r\n            head.prev = region;\r\n        }\r\n        region.next = head;\r\n        region.prev = null;\r\n        head = region;\r\n\r\n        // Initialize the data block\r\n        void* memoryPointer = p + regionEntrySize;\r\n        Block block1 = cast(Block) memoryPointer;\r\n        block1.size = size;\r\n        block1.free = false;\r\n\r\n        // It is what we want to return\r\n        void* data = memoryPointer + blockEntrySize;\r\n\r\n        // Free block after data\r\n        memoryPointer = data + size;\r\n        Block block2 = cast(Block) memoryPointer;\r\n        block1.prev = block2.next = null;\r\n        block1.next = block2;\r\n        block2.prev = block1;\r\n        block2.size = regionSize - size - regionEntrySize - blockEntrySize * 2;\r\n        block2.free = true;\r\n        block1.region = block2.region = region;\r\n\r\n        return data;\r\n    }\r\n\r\n    /// Ditto.\r\n    private void* initializeRegion(size_t size) @nogc nothrow\r\n    {\r\n        return initializeRegion(size, head);\r\n    }\r\n\r\n    /**\r\n     * Params:\r\n     *     x = Space to be aligned.\r\n     *\r\n     * Returns: Aligned size of $(D_PARAM x).\r\n     */\r\n    pragma(inline)\r\n    private static immutable(size_t) addAlignment(size_t x)\r\n    @nogc @safe pure nothrow\r\n    out (result)\r\n    {\r\n        assert(result > 0);\r\n    }\r\n    do\r\n    {\r\n        return (x - 1) / alignment_ * alignment_ + alignment_;\r\n    }\r\n\r\n    /**\r\n     * Params:\r\n     *     x = Required space.\r\n     *\r\n     * Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)).\r\n     */\r\n    pragma(inline)\r\n    private static immutable(size_t) calculateRegionSize(size_t x)\r\n    @nogc nothrow\r\n    out (result)\r\n    {\r\n        assert(result > 0);\r\n    }\r\n    do\r\n    {\r\n        x += regionEntrySize + blockEntrySize * 2;\r\n        return x / pageSize * pageSize + pageSize;\r\n    }\r\n\r\n    @property immutable(uint) alignment() const @nogc @safe pure nothrow\r\n    {\r\n        return alignment_;\r\n    }\r\n    private enum alignment_ = 8;\r\n\r\n    private static __gshared MmapPool instance_;\r\n\r\n    private static __gshared size_t pageSize;\r\n\r\n    private struct RegionEntry\r\n    {\r\n        Region prev;\r\n        Region next;\r\n        uint blocks;\r\n        size_t size;\r\n    }\r\n    private alias Region = RegionEntry*;\r\n    private enum regionEntrySize = 32;\r\n\r\n    private Region head;\r\n\r\n    private struct BlockEntry\r\n    {\r\n        Block prev;\r\n        Block next;\r\n        bool free;\r\n        size_t size;\r\n        Region region;\r\n    }\r\n    private alias Block = BlockEntry*;\r\n    private enum blockEntrySize = 40;\r\n}\n"
  },
  {
    "path": "dlib/memory/package.d",
    "content": "/*\nCopyright (c) 2016-2025 Eugene Wissner\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Allocators and memory management functions\n *\n * Copyright: Eugene Wissner 2016-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Eugene Wissner\n */\nmodule dlib.memory;\n\npublic\n{\n    import dlib.memory.allocator;\n    import dlib.memory.gcallocator;\n    import dlib.memory.mallocator;\n    import dlib.memory.mmappool;\n    import dlib.memory.arena;\n    \n    import std.experimental.allocator: make, dispose, shrinkArray, expandArray, makeArray, dispose;\n}\n\nAllocator allocator;\r\n\n/// Get default allocator (Mallocator)\r\n@property Allocator defaultAllocator()\r\n{\r\n    if (allocator is null)\r\n    {\r\n        allocator = Mallocator.instance;\r\n    }\r\n    return allocator;\r\n}\r\n"
  },
  {
    "path": "dlib/network/errno.d",
    "content": "/*\nCopyright (c) 2020-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\nmodule dlib.network.errno;\n\npublic import core.stdc.errno;\n\nversion(Windows)\n{\n    import core.sys.windows.winsock2;\n    \n    enum: int\n    {\n        ECONNABORTED = core.sys.windows.winsock2.ECONNABORTED,\n        ENOBUFS = core.sys.windows.winsock2.ENOBUFS,\n        EOPNOTSUPP = core.sys.windows.winsock2.EOPNOTSUPP,\n        EPROTONOSUPPORT = core.sys.windows.winsock2.EPROTONOSUPPORT,\n        EPROTOTYPE = core.sys.windows.winsock2.EPROTOTYPE,\n        ESOCKTNOSUPPORT = core.sys.windows.winsock2.ESOCKTNOSUPPORT,\n        ETIMEDOUT = core.sys.windows.winsock2.ETIMEDOUT,\n        EWOULDBLOCK = core.sys.windows.winsock2.EWOULDBLOCK\n    }\n}\nelse version (OSX)\n{\n    version = MacBSD;\n}\nelse version (iOS)\n{\n    version = MacBSD;\n}\nelse version (FreeBSD)\n{\n    version = MacBSD;\n}\nelse version (OpenBSD)\n{\n    version = MacBSD;\n}\nelse version (DragonFlyBSD)\n{\n    version = MacBSD;\n}\n\nversion (MacBSD)\n{\n    enum: int\n    {\n        ESOCKTNOSUPPORT = 44\n    }\n}\n"
  },
  {
    "path": "dlib/network/package.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Eugene Wissner, Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Networking and web functionality\r\n *\r\n * Copyright: Eugene Wissner, Timur Gafarov 2016-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Eugene Wissner, Timur Gafarov\r\n */\r\nmodule dlib.network;\r\n\r\npublic\r\n{\r\n    import dlib.network.socket;\r\n    import dlib.network.url;\r\n}\r\n"
  },
  {
    "path": "dlib/network/socket.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Eugene Wissner\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Platform-independent socket API\r\n *\r\n * Copyright: Eugene Wissner 2016-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Eugene Wissner\r\n */\r\nmodule dlib.network.socket;\r\n\r\nimport dlib.memory;\r\nimport err = dlib.network.errno;\r\nimport core.time;\r\nimport std.algorithm.comparison;\r\nimport std.algorithm.searching;\r\npublic import std.socket: AddressException, socket_t, Linger, SocketOptionLevel,\r\n                          SocketType, AddressFamily, AddressInfo,\r\n                          SocketOption;\r\nimport std.traits;\r\nimport std.typecons;\r\n\r\nversion (Posix)\r\n{\r\n    import core.sys.posix.fcntl;\r\n    import core.sys.posix.netdb;\r\n    import core.sys.posix.netinet.in_;\r\n    import core.sys.posix.sys.socket;\r\n    import core.sys.posix.sys.time;\r\n    import core.sys.posix.unistd;\r\n\r\n    private enum SOCKET_ERROR = -1;\r\n}\r\nelse version (Windows)\r\n{\r\n    //import dlib.async.iocp;\r\n    import core.sys.windows.winbase;\r\n    import core.sys.windows.windef;\r\n\r\n    import core.sys.windows.basetyps;\r\n    import core.sys.windows.mswsock;\r\n    import core.sys.windows.winbase;\r\n    import core.sys.windows.windef;\r\n    import core.sys.windows.winsock2;\r\n\r\n    enum : uint\r\n    {\r\n        IOC_UNIX     = 0x00000000,\r\n        IOC_WS2      = 0x08000000,\r\n        IOC_PROTOCOL = 0x10000000,\r\n        IOC_VOID     = 0x20000000,         /// No parameters.\r\n        IOC_OUT      = 0x40000000,         /// Copy parameters back.\r\n        IOC_IN       = 0x80000000,         /// Copy parameters into.\r\n        IOC_VENDOR   = 0x18000000,\r\n        IOC_INOUT    = (IOC_IN | IOC_OUT), /// Copy parameter into and get back.\r\n    }\r\n\r\n    template _WSAIO(int x, int y)\r\n    {\r\n        enum _WSAIO = IOC_VOID | x | y;\r\n    }\r\n    template _WSAIOR(int x, int y)\r\n    {\r\n        enum _WSAIOR = IOC_OUT | x | y;\r\n    }\r\n    template _WSAIOW(int x, int y)\r\n    {\r\n        enum _WSAIOW = IOC_IN | x | y;\r\n    }\r\n    template _WSAIORW(int x, int y)\r\n    {\r\n        enum _WSAIORW = IOC_INOUT | x | y;\r\n    }\r\n\r\n    alias SIO_ASSOCIATE_HANDLE               = _WSAIOW!(IOC_WS2, 1);\r\n    alias SIO_ENABLE_CIRCULAR_QUEUEING       = _WSAIO!(IOC_WS2, 2);\r\n    alias SIO_FIND_ROUTE                     = _WSAIOR!(IOC_WS2, 3);\r\n    alias SIO_FLUSH                          = _WSAIO!(IOC_WS2, 4);\r\n    alias SIO_GET_BROADCAST_ADDRESS          = _WSAIOR!(IOC_WS2, 5);\r\n    alias SIO_GET_EXTENSION_FUNCTION_POINTER = _WSAIORW!(IOC_WS2, 6);\r\n    alias SIO_GET_QOS                        = _WSAIORW!(IOC_WS2, 7);\r\n    alias SIO_GET_GROUP_QOS                  = _WSAIORW!(IOC_WS2, 8);\r\n    alias SIO_MULTIPOINT_LOOPBACK            = _WSAIOW!(IOC_WS2, 9);\r\n    alias SIO_MULTICAST_SCOPE                = _WSAIOW!(IOC_WS2, 10);\r\n    alias SIO_SET_QOS                        = _WSAIOW!(IOC_WS2, 11);\r\n    alias SIO_SET_GROUP_QOS                  = _WSAIOW!(IOC_WS2, 12);\r\n    alias SIO_TRANSLATE_HANDLE               = _WSAIORW!(IOC_WS2, 13);\r\n    alias SIO_ROUTING_INTERFACE_QUERY        = _WSAIORW!(IOC_WS2, 20);\r\n    alias SIO_ROUTING_INTERFACE_CHANGE       = _WSAIOW!(IOC_WS2, 21);\r\n    alias SIO_ADDRESS_LIST_QUERY             = _WSAIOR!(IOC_WS2, 22);\r\n    alias SIO_ADDRESS_LIST_CHANGE            = _WSAIO!(IOC_WS2, 23);\r\n    alias SIO_QUERY_TARGET_PNP_HANDLE        = _WSAIOR!(IOC_WS2, 24);\r\n    alias SIO_NSP_NOTIFY_CHANGE              = _WSAIOW!(IOC_WS2, 25);\r\n\r\n    private alias GROUP = uint;\r\n\r\n    enum\r\n    {\r\n        WSA_FLAG_OVERLAPPED = 0x01,\r\n        MAX_PROTOCOL_CHAIN = 7,\r\n        WSAPROTOCOL_LEN = 255,\r\n    }\r\n\r\n    struct WSAPROTOCOLCHAIN\r\n    {\r\n        int                       ChainLen;\r\n        DWORD[MAX_PROTOCOL_CHAIN] ChainEntries;\r\n    }\r\n    alias LPWSAPROTOCOLCHAIN = WSAPROTOCOLCHAIN*;\r\n\r\n    struct WSAPROTOCOL_INFO\r\n    {\r\n        DWORD                      dwServiceFlags1;\r\n        DWORD                      dwServiceFlags2;\r\n        DWORD                      dwServiceFlags3;\r\n        DWORD                      dwServiceFlags4;\r\n        DWORD                      dwProviderFlags;\r\n        GUID                       ProviderId;\r\n        DWORD                      dwCatalogEntryId;\r\n        WSAPROTOCOLCHAIN           ProtocolChain;\r\n        int                        iVersion;\r\n        int                        iAddressFamily;\r\n        int                        iMaxSockAddr;\r\n        int                        iMinSockAddr;\r\n        int                        iSocketType;\r\n        int                        iProtocol;\r\n        int                        iProtocolMaxOffset;\r\n        int                        iNetworkByteOrder;\r\n        int                        iSecurityScheme;\r\n        DWORD                      dwMessageSize;\r\n        DWORD                      dwProviderReserved;\r\n        TCHAR[WSAPROTOCOL_LEN + 1] szProtocol;\r\n    }\r\n    alias LPWSAPROTOCOL_INFO = WSAPROTOCOL_INFO*;\r\n\r\n    extern (Windows) @nogc nothrow\r\n    {\r\n        private SOCKET WSASocketW(int af,\r\n                                  int type,\r\n                                  int protocol,\r\n                                  LPWSAPROTOCOL_INFO lpProtocolInfo,\r\n                                  GROUP g,\r\n                                  DWORD dwFlags);\r\n        int WSARecv(SOCKET s,\r\n                    LPWSABUF lpBuffers,\r\n                    DWORD dwBufferCount,\r\n                    LPDWORD lpNumberOfBytesRecvd,\r\n                    LPDWORD lpFlags,\r\n                    LPOVERLAPPED lpOverlapped,\r\n                    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);\r\n        int WSASend(SOCKET s,\r\n                    LPWSABUF lpBuffers,\r\n                    DWORD dwBufferCount,\r\n                    LPDWORD lpNumberOfBytesRecvd,\r\n                    DWORD lpFlags,\r\n                    LPOVERLAPPED lpOverlapped,\r\n                    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);\r\n    }\r\n    alias WSASocket = WSASocketW;\r\n\r\n    alias LPFN_GETACCEPTEXSOCKADDRS = VOID function(PVOID,\r\n                                                    DWORD,\r\n                                                    DWORD,\r\n                                                    DWORD,\r\n                                                    SOCKADDR**,\r\n                                                    LPINT,\r\n                                                    SOCKADDR**,\r\n                                                    LPINT);\r\n    const GUID WSAID_GETACCEPTEXSOCKADDRS = {0xb5367df2,0xcbac,0x11cf,[0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92]};\r\n\r\n    struct WSABUF\r\n    {\r\n        ULONG len;\r\n        CHAR* buf;\r\n    }\r\n    alias WSABUF* LPWSABUF;\r\n\r\n    struct WSAOVERLAPPED\r\n    {\r\n        ULONG_PTR Internal;\r\n        ULONG_PTR InternalHigh;\r\n        union\r\n        {\r\n            struct\r\n            {\r\n                DWORD Offset;\r\n                DWORD OffsetHigh;\r\n            }\r\n            PVOID  Pointer;\r\n        }\r\n        HANDLE hEvent;\r\n    }\r\n    alias LPWSAOVERLAPPED = WSAOVERLAPPED*;\r\n\r\n    enum SO_UPDATE_ACCEPT_CONTEXT = 0x700B;\r\n\r\n    enum OverlappedSocketEvent\r\n    {\r\n        accept = 1,\r\n        read = 2,\r\n        write = 3,\r\n    }\r\n\r\n    class State\r\n    {\r\n        /// For internal use by Windows API.\r\n        align(1) OVERLAPPED overlapped;\r\n\r\n        /// File/socket handle.\r\n        HANDLE handle;\r\n\r\n        /// For keeping events or event masks.\r\n        int event;\r\n    }\r\n\r\n    class SocketState : State\r\n    {\r\n        private WSABUF buffer;\r\n    }\r\n\r\n    /**\r\n     * Socket returned if a connection has been established.\r\n     */\r\n    class OverlappedConnectedSocket : ConnectedSocket\r\n    {\r\n        /**\r\n         * Create a socket.\r\n         *\r\n         * Params:\r\n         *     handle   = Socket handle.\r\n         *     af       = Address family.\r\n         */\r\n        this(socket_t handle, AddressFamily af)\r\n        {\r\n            super(handle, af);\r\n        }\r\n\r\n        /**\r\n         * Begins to asynchronously receive data from a connected socket.\r\n         *\r\n         * Params:\r\n         *     buffer     = Storage location for the received data.\r\n         *     flags      = Flags.\r\n         *     overlapped = Unique operation identifier.\r\n         *\r\n         * Returns: $(D_KEYWORD true) if the operation could be finished synchronously.\r\n         *          $(D_KEYWORD false) otherwise.\r\n         *\r\n         * Throws: $(D_PSYMBOL SocketException) if unable to receive.\r\n         */\r\n        bool beginReceive(ubyte[] buffer,\r\n                          SocketState overlapped,\r\n                          Flags flags = Flags(Flag.none)) @trusted\r\n        {\r\n            auto receiveFlags = cast(DWORD) flags;\r\n\r\n            overlapped.handle = cast(HANDLE) handle_;\r\n            overlapped.event = OverlappedSocketEvent.read;\r\n            overlapped.buffer.len = cast(uint) buffer.length;\r\n            overlapped.buffer.buf = cast(char*) buffer.ptr;\r\n\r\n            auto result = WSARecv(handle_,\r\n                                  &overlapped.buffer,\r\n                                  1u,\r\n                                  NULL,\r\n                                  &receiveFlags,\r\n                                  &overlapped.overlapped,\r\n                                  NULL);\r\n\r\n            if (result == SOCKET_ERROR && !wouldHaveBlocked)\r\n            {\r\n                throw defaultAllocator.make!SocketException(\"Unable to receive\");\r\n            }\r\n            return result == 0;\r\n        }\r\n\r\n        /**\r\n         * Ends a pending asynchronous read.\r\n         *\r\n         * Params\r\n         *     overlapped = Unique operation identifier.\r\n         *\r\n         * Returns: Number of bytes received.\r\n         *\r\n         * Throws: $(D_PSYMBOL SocketException) if unable to receive.\r\n         */\r\n        int endReceive(SocketState overlapped) @trusted\r\n        out (count)\r\n        {\r\n            assert(count >= 0);\r\n        }\r\n        do\r\n        {\r\n            DWORD lpNumber;\r\n            BOOL result = GetOverlappedResult(overlapped.handle,\r\n                                              &overlapped.overlapped,\r\n                                              &lpNumber,\r\n                                              FALSE);\r\n            if (result == FALSE && !wouldHaveBlocked)\r\n            {\r\n                disconnected_ = true;\r\n                throw defaultAllocator.make!SocketException(\"Unable to receive\");\r\n            }\r\n            if (lpNumber == 0)\r\n            {\r\n                disconnected_ = true;\r\n            }\r\n            return lpNumber;\r\n        }\r\n\r\n        /**\r\n         * Sends data asynchronously to a connected socket.\r\n         *\r\n         * Params:\r\n         *     buffer     = Data to be sent.\r\n         *     flags      = Flags.\r\n         *     overlapped = Unique operation identifier.\r\n         *\r\n         * Returns: $(D_KEYWORD true) if the operation could be finished synchronously.\r\n         *          $(D_KEYWORD false) otherwise.\r\n         *\r\n         * Throws: $(D_PSYMBOL SocketException) if unable to send.\r\n         */\r\n        bool beginSend(ubyte[] buffer,\r\n                       SocketState overlapped,\r\n                       Flags flags = Flags(Flag.none)) @trusted\r\n        {\r\n            overlapped.handle = cast(HANDLE) handle_;\r\n            overlapped.event = OverlappedSocketEvent.write;\r\n            overlapped.buffer.len = cast(uint) buffer.length;\r\n            overlapped.buffer.buf = cast(char*) buffer.ptr;\r\n\r\n            auto result = WSASend(handle_,\r\n                                  &overlapped.buffer,\r\n                                  1u,\r\n                                  NULL,\r\n                                  cast(DWORD) flags,\r\n                                  &overlapped.overlapped,\r\n                                  NULL);\r\n\r\n            if (result == SOCKET_ERROR && !wouldHaveBlocked)\r\n            {\r\n                disconnected_ = true;\r\n                throw defaultAllocator.make!SocketException(\"Unable to send\");\r\n            }\r\n            return result == 0;\r\n        }\r\n\r\n        /**\r\n         * Ends a pending asynchronous send.\r\n         *\r\n         * Params\r\n         *     overlapped = Unique operation identifier.\r\n         *\r\n         * Returns: Number of bytes sent.\r\n         *\r\n         * Throws: $(D_PSYMBOL SocketException) if unable to receive.\r\n        */\r\n        int endSend(SocketState overlapped) @trusted\r\n        out (count)\r\n        {\r\n            assert(count >= 0);\r\n        }\r\n        do\r\n        {\r\n            DWORD lpNumber;\r\n            BOOL result = GetOverlappedResult(overlapped.handle,\r\n                                              &overlapped.overlapped,\r\n                                              &lpNumber,\r\n                                              FALSE);\r\n            if (result == FALSE && !wouldHaveBlocked)\r\n            {\r\n                disconnected_ = true;\r\n                throw defaultAllocator.make!SocketException(\"Unable to receive\");\r\n            }\r\n            return lpNumber;\r\n        }\r\n    }\r\n\r\n    class OverlappedStreamSocket : StreamSocket\r\n    {\r\n        /// Accept extension function pointer.\r\n        package LPFN_ACCEPTEX acceptExtension;\r\n\r\n        /**\r\n         * Create a socket.\r\n         *\r\n         * Params:\r\n         *     af       = Address family.\r\n         *\r\n         * Throws: $(D_PSYMBOL SocketException) on errors.\r\n         */\r\n        this(AddressFamily af) @trusted\r\n        {\r\n            super(af);\r\n            scope (failure)\r\n            {\r\n                this.close();\r\n            }\r\n            blocking = false;\r\n\r\n            GUID guidAcceptEx = WSAID_ACCEPTEX;\r\n            DWORD dwBytes;\r\n\r\n            auto result = WSAIoctl(handle_,\r\n                                   SIO_GET_EXTENSION_FUNCTION_POINTER,\r\n                                   &guidAcceptEx,\r\n                                   guidAcceptEx.sizeof,\r\n                                   &acceptExtension,\r\n                                   acceptExtension.sizeof,\r\n                                   &dwBytes,\r\n                                   NULL,\r\n                                   NULL);\r\n            if (!result == SOCKET_ERROR)\r\n            {\r\n                throw defaultAllocator.make!SocketException(\"Unable to retrieve an accept extension function pointer\");\r\n            }\r\n        }\r\n\r\n        /**\r\n         * Begins an asynchronous operation to accept an incoming connection attempt.\r\n         *\r\n         * Params:\r\n         *     overlapped = Unique operation identifier.\r\n         *\r\n         * Returns: $(D_KEYWORD true) if the operation could be finished synchronously.\r\n         *          $(D_KEYWORD false) otherwise.\r\n         *\r\n         * Throws: $(D_PSYMBOL SocketException) on accept errors.\r\n         */\r\n        bool beginAccept(SocketState overlapped) @trusted\r\n        {\r\n            auto socket = cast(socket_t) socket(addressFamily, SOCK_STREAM, 0);\r\n            if (socket == socket_t.init)\r\n            {\r\n                throw defaultAllocator.make!SocketException(\"Unable to create socket\");\r\n            }\r\n            scope (failure)\r\n            {\r\n                closesocket(socket);\r\n            }\r\n            DWORD dwBytes;\r\n            overlapped.handle = cast(HANDLE) socket;\r\n            overlapped.event = OverlappedSocketEvent.accept;\r\n            overlapped.buffer.len = (sockaddr_in.sizeof + 16) * 2;\r\n            overlapped.buffer.buf = defaultAllocator.makeArray!char(overlapped.buffer.len).ptr;\r\n\r\n            // We don't want to get any data now, but only start to accept the connections\r\n            BOOL result = acceptExtension(handle_,\r\n                                          socket,\r\n                                          overlapped.buffer.buf,\r\n                                          0u,\r\n                                          sockaddr_in.sizeof + 16,\r\n                                          sockaddr_in.sizeof + 16,\r\n                                          &dwBytes,\r\n                                          &overlapped.overlapped);\r\n            if (result == FALSE && !wouldHaveBlocked)\r\n            {\r\n                throw defaultAllocator.make!SocketException(\"Unable to accept socket connection\");\r\n            }\r\n            return result == TRUE;\r\n        }\r\n\r\n        /**\r\n         * Asynchronously accepts an incoming connection attempt and creates a\r\n         * new socket to handle remote host communication.\r\n         *\r\n         * Params:\r\n         *     overlapped = Unique operation identifier.\r\n         *\r\n         * Returns: Connected socket.\r\n         *\r\n         * Throws: $(D_PSYMBOL SocketException) if unable to accept.\r\n         */\r\n        OverlappedConnectedSocket endAccept(SocketState overlapped) @trusted\r\n        {\r\n            scope (exit)\r\n            {\r\n                defaultAllocator.dispose(overlapped.buffer.buf[0..overlapped.buffer.len]);\r\n            }\r\n            auto socket = defaultAllocator.make!OverlappedConnectedSocket(cast(socket_t) overlapped.handle,\r\n                                                                          addressFamily);\r\n            scope (failure)\r\n            {\r\n                defaultAllocator.dispose(socket);\r\n            }\r\n            socket.setOption(SocketOptionLevel.SOCKET,\r\n                             cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT,\r\n                             cast(size_t) handle);\r\n            return socket;\r\n        }\r\n    }\r\n}\r\n\r\nversion (linux)\r\n{\r\n    enum SOCK_NONBLOCK = O_NONBLOCK;\r\n    extern(C) int accept4(int, sockaddr*, socklen_t*, int flags) @nogc nothrow;\r\n}\r\n\r\nprivate immutable\r\n{\r\n    typeof(&getaddrinfo) getaddrinfoPointer;\r\n    typeof(&freeaddrinfo) freeaddrinfoPointer;\r\n}\r\n\r\nshared static this()\r\n{\r\n    version (Windows)\r\n    {\r\n        auto ws2Lib = GetModuleHandle(\"ws2_32.dll\");\r\n\r\n        getaddrinfoPointer = cast(typeof(getaddrinfoPointer))\r\n                             GetProcAddress(ws2Lib, \"getaddrinfo\");\r\n        freeaddrinfoPointer = cast(typeof(freeaddrinfoPointer))\r\n                              GetProcAddress(ws2Lib, \"freeaddrinfo\");\r\n    }\r\n    else version (Posix)\r\n    {\r\n        getaddrinfoPointer = &getaddrinfo;\r\n        freeaddrinfoPointer = &freeaddrinfo;\r\n    }\r\n}\r\n\r\n/**\r\n * Error codes for $(D_PSYMBOL Socket).\r\n */\r\nenum SocketError : int\r\n{\r\n    /// Unknown error\r\n    unknown                = 0,\r\n    /// Firewall rules forbid connection.\r\n    accessDenied           = err.EPERM,\r\n    /// A socket operation was attempted on a non-socket.\r\n    notSocket              = err.EBADF,\r\n    /// The network is not available.\r\n    networkDown            = err.ECONNABORTED,\r\n    /// An invalid pointer address was detected by the underlying socket provider.\r\n    fault                  = err.EFAULT,\r\n    /// An invalid argument was supplied to a $(D_PSYMBOL Socket) member.\r\n    invalidArgument        = err.EINVAL,\r\n    /// The limit on the number of open sockets has been reached.\r\n    tooManyOpenSockets     = err.ENFILE,\r\n    /// No free buffer space is available for a Socket operation.\r\n    noBufferSpaceAvailable = err.ENOBUFS,\r\n    /// The address family is not supported by the protocol family.\r\n    operationNotSupported  = err.EOPNOTSUPP,\r\n    /// The protocol is not implemented or has not been configured.\r\n    protocolNotSupported   = err.EPROTONOSUPPORT,\r\n    /// Protocol error.\r\n    protocolError          = err.EPROTOTYPE,\r\n    /// The connection attempt timed out, or the connected host has failed to respond.\r\n    timedOut               = err.ETIMEDOUT,\r\n    /// The support for the specified socket type does not exist in this address family.\r\n    socketNotSupported     = err.ESOCKTNOSUPPORT,\r\n}\r\n\r\n/**\r\n * $(D_PSYMBOL SocketException) should be thrown only if one of the socket functions\r\n * returns -1 or $(D_PSYMBOL SOCKET_ERROR) and sets $(D_PSYMBOL errno),\r\n * because $(D_PSYMBOL SocketException) relies on the $(D_PSYMBOL errno) value.\r\n */\r\nclass SocketException : Exception\r\n{\r\n    immutable SocketError error = SocketError.unknown;\r\n\r\n    /**\r\n     * Params:\r\n     *     msg  = The message for the exception.\r\n     *     file = The file where the exception occurred.\r\n     *     line = The line number where the exception occurred.\r\n     *     next = The previous exception in the chain of exceptions, if any.\r\n     */\r\n    this(string msg,\r\n         string file = __FILE__,\r\n         size_t line = __LINE__,\r\n         Throwable next = null) @nogc @safe nothrow\r\n    {\r\n        super(msg, file, line, next);\r\n\r\n        foreach (member; EnumMembers!SocketError)\r\n        {\r\n            if (member == lastError)\r\n            {\r\n                error = member;\r\n                return;\r\n            }\r\n        }\r\n        if (lastError == err.ENOMEM)\r\n        {\r\n            error = SocketError.noBufferSpaceAvailable;\r\n        }\r\n        else if (lastError == err.EMFILE)\r\n        {\r\n            error = SocketError.tooManyOpenSockets;\r\n        }\r\n        else version (linux)\r\n        {\r\n            if (lastError == err.ENOSR)\r\n            {\r\n                error = SocketError.networkDown;\r\n            }\r\n        }\r\n        else version (Posix)\r\n        {\r\n            if (lastError == err.EPROTO)\r\n            {\r\n                error = SocketError.networkDown;\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/**\r\n * Class for creating a network communication endpoint using the Berkeley\r\n * sockets interfaces of different types.\r\n */\r\nabstract class Socket\r\n{\r\n    version (Posix)\r\n    {\r\n        /**\r\n         * How a socket is shutdown.\r\n         */\r\n        enum Shutdown : int\r\n        {\r\n            receive = SHUT_RD,   /// Socket receives are disallowed\r\n            send    = SHUT_WR,   /// Socket sends are disallowed\r\n            both    = SHUT_RDWR, /// Both receive and send\r\n        }\r\n    }\r\n    else version (Windows)\r\n    {\r\n        /// Property to get or set whether the socket is blocking or nonblocking.\r\n        private bool blocking_ = true;\r\n\r\n        /**\r\n         * How a socket is shutdown.\r\n         */\r\n        enum Shutdown : int\r\n        {\r\n            receive = SD_RECEIVE, /// Socket receives are disallowed.\r\n            send    = SD_SEND,    /// Socket sends are disallowed.\r\n            both    = SD_BOTH,    /// Both receive and send.\r\n        }\r\n\r\n        // The WinSock timeouts seem to be effectively skewed by a constant\r\n        // offset of about half a second (in milliseconds).\r\n        private enum WINSOCK_TIMEOUT_SKEW = 500;\r\n    }\r\n\r\n    /// Socket handle.\r\n    protected socket_t handle_;\r\n\r\n    /// Address family.\r\n    protected AddressFamily family;\r\n\r\n    private @property void handle(socket_t handle)\r\n    in\r\n    {\r\n        assert(handle != socket_t.init);\r\n        assert(handle_ == socket_t.init, \"Socket handle cannot be changed\");\r\n    }\r\n    do\r\n    {\r\n        handle_ = handle;\r\n\r\n        // Set the option to disable SIGPIPE on send() if the platform\r\n        // has it (e.g. on OS X).\r\n        static if (is(typeof(SO_NOSIGPIPE)))\r\n        {\r\n            setOption(SocketOptionLevel.SOCKET, cast(SocketOption)SO_NOSIGPIPE, true);\r\n        }\r\n    }\r\n\r\n    @property inout(socket_t) handle() inout const pure nothrow @safe @nogc\r\n    {\r\n        return handle_;\r\n    }\r\n\r\n    /**\r\n     * Create a socket.\r\n     *\r\n     * Params:\r\n     *     handle   = Socket.\r\n     *     af       = Address family.\r\n     */\r\n    this(socket_t handle, AddressFamily af)\r\n    in\r\n    {\r\n        assert(handle != socket_t.init);\r\n    }\r\n    do\r\n    {\r\n        scope (failure)\r\n        {\r\n            this.close();\r\n        }\r\n        this.handle = handle;\r\n        family = af;\r\n    }\r\n\r\n    /**\r\n     * Closes the socket and calls the destructor on itself.\r\n     */\r\n    ~this() nothrow @trusted @nogc\r\n    {\r\n        this.close();\r\n    }\r\n\r\n    /**\r\n     * Get a socket option.\r\n     *\r\n     * Params:\r\n     *     level  = Protocol level at that the option exists.\r\n     *     option = Option.\r\n     *     result = Buffer to save the result.\r\n     *\r\n     * Returns: The number of bytes written to $(D_PARAM result).\r\n     *\r\n     * Throws: $(D_PSYMBOL SocketException) on error.\r\n     */\r\n    protected int getOption(SocketOptionLevel level, SocketOption option, void[] result) const @trusted\r\n    {\r\n        auto length = cast(socklen_t) result.length;\r\n        if (getsockopt(handle_,\r\n                       cast(int) level,\r\n                       cast(int) option,\r\n                       result.ptr,\r\n                       &length) == SOCKET_ERROR)\r\n        {\r\n            throw defaultAllocator.make!SocketException(\"Unable to get socket option\");\r\n        }\r\n        return length;\r\n    }\r\n\r\n    /// Ditto.\r\n    int getOption(SocketOptionLevel level, SocketOption option, out size_t result) const @trusted\r\n    {\r\n        return getOption(level, option, (&result)[0..1]);\r\n    }\r\n\r\n    /// Ditto.\r\n    int getOption(SocketOptionLevel level, SocketOption option, out Linger result) const @trusted\r\n    {\r\n        return getOption(level, option, (&result.clinger)[0..1]);\r\n    }\r\n\r\n    /// Ditto.\r\n    int getOption(SocketOptionLevel level, SocketOption option, out Duration result) const @trusted\r\n    {\r\n        // WinSock returns the timeout values as a milliseconds DWORD,\r\n        // while Linux and BSD return a timeval struct.\r\n        version (Posix)\r\n        {\r\n            timeval tv;\r\n            auto ret = getOption(level, option, (&tv)[0..1]);\r\n            result = dur!\"seconds\"(tv.tv_sec) + dur!\"usecs\"(tv.tv_usec);\r\n        }\r\n        else version (Windows)\r\n        {\r\n            int msecs;\r\n            auto ret = getOption(level, option, (&msecs)[0 .. 1]);\r\n            if (option == SocketOption.RCVTIMEO)\r\n            {\r\n                msecs += WINSOCK_TIMEOUT_SKEW;\r\n            }\r\n            result = dur!\"msecs\"(msecs);\r\n        }\r\n        return ret;\r\n    }\r\n\r\n    /**\r\n     * Set a socket option.\r\n     *\r\n     * Params:\r\n     *     level  = Protocol level at that the option exists.\r\n     *     option = Option.\r\n     *     value = Option value.\r\n     *\r\n     * Throws: $(D_PSYMBOL SocketException) on error.\r\n     */\r\n    protected void setOption(SocketOptionLevel level, SocketOption option, void[] value)\r\n    const @trusted\r\n    {\r\n        if (setsockopt(handle_,\r\n                       cast(int)level,\r\n                       cast(int)option,\r\n                       value.ptr,\r\n                       cast(uint) value.length) == SOCKET_ERROR)\r\n        {\r\n            throw defaultAllocator.make!SocketException(\"Unable to set socket option\");\r\n        }\r\n    }\r\n\r\n    /// Ditto.\r\n    void setOption(SocketOptionLevel level, SocketOption option, size_t value) const @trusted\r\n    {\r\n        setOption(level, option, (&value)[0..1]);\r\n    }\r\n\r\n    /// Ditto.\r\n    void setOption(SocketOptionLevel level, SocketOption option, Linger value) const @trusted\r\n    {\r\n        setOption(level, option, (&value.clinger)[0..1]);\r\n    }\r\n\r\n    /// Ditto.\r\n    void setOption(SocketOptionLevel level, SocketOption option, Duration value) const @trusted\r\n    {\r\n        version (Posix)\r\n        {\r\n            timeval tv;\r\n            value.split!(\"seconds\", \"usecs\")(tv.tv_sec, tv.tv_usec);\r\n            setOption(level, option, (&tv)[0..1]);\r\n        }\r\n        else version (Windows)\r\n        {\r\n            auto msecs = cast(int) value.total!\"msecs\";\r\n            if (msecs > 0 && option == SocketOption.RCVTIMEO)\r\n            {\r\n                msecs = max(1, msecs - WINSOCK_TIMEOUT_SKEW);\r\n            }\r\n            setOption(level, option, msecs);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns: Socket's blocking flag.\r\n     */\r\n    @property inout(bool) blocking() inout const nothrow @nogc\r\n    {\r\n        version (Posix)\r\n        {\r\n            return !(fcntl(handle_, F_GETFL, 0) & O_NONBLOCK);\r\n        }\r\n        else version (Windows)\r\n        {\r\n            return blocking_;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Params:\r\n     *     yes = Socket's blocking flag.\r\n     */\r\n    @property void blocking(bool yes)\r\n    {\r\n        version (Posix)\r\n        {\r\n            int fl = fcntl(handle_, F_GETFL, 0);\r\n\r\n            if (fl != SOCKET_ERROR)\r\n            {\r\n                fl = yes ? fl & ~O_NONBLOCK : fl | O_NONBLOCK;\r\n                fl = fcntl(handle_, F_SETFL, fl);\r\n            }\r\n            if (fl == SOCKET_ERROR)\r\n            {\r\n                throw defaultAllocator.make!SocketException(\"Unable to set socket blocking\");\r\n            }\r\n        }\r\n        else version (Windows)\r\n        {\r\n            uint num = !yes;\r\n            if (ioctlsocket(handle_, FIONBIO, &num) == SOCKET_ERROR)\r\n            {\r\n                throw defaultAllocator.make!SocketException(\"Unable to set socket blocking\");\r\n            }\r\n            blocking_ = yes;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns: The socket's address family.\r\n     */\r\n    @property AddressFamily addressFamily() const @nogc @safe pure nothrow\r\n    {\r\n        return family;\r\n    }\r\n\r\n    /**\r\n     * Returns: $(D_KEYWORD true) if this is a valid, alive socket.\r\n     */\r\n    @property bool isAlive() @trusted const nothrow @nogc\r\n    {\r\n        int type;\r\n        socklen_t typesize = cast(socklen_t) type.sizeof;\r\n        return !getsockopt(handle_, SOL_SOCKET, SO_TYPE, cast(char*)&type, &typesize);\r\n    }\r\n\r\n    /**\r\n     * Disables sends and/or receives.\r\n     *\r\n     * Params:\r\n     *     how = What to disable.\r\n     *\r\n     * See_Also:\r\n     *     $(D_PSYMBOL Shutdown)\r\n     */\r\n    void shutdown(Shutdown how = Shutdown.both) @nogc @trusted const nothrow\r\n    {\r\n        .shutdown(handle_, cast(int)how);\r\n    }\r\n\r\n    /**\r\n     * Immediately drop any connections and release socket resources.\r\n     * Calling $(D_PSYMBOL shutdown) before $(D_PSYMBOL close) is recommended\r\n     * for connection-oriented sockets. The $(D_PSYMBOL Socket) object is no\r\n     * longer usable after $(D_PSYMBOL close).\r\n     */\r\n    void close() nothrow @trusted @nogc\r\n    {\r\n        version(Windows)\r\n        {\r\n            .closesocket(handle_);\r\n        }\r\n        else version(Posix)\r\n        {\r\n            .close(handle_);\r\n        }\r\n        handle_ = socket_t.init;\r\n    }\r\n\r\n    /**\r\n     * Listen for an incoming connection. $(D_PSYMBOL bind) must be called before you\r\n     * can $(D_PSYMBOL listen).\r\n     *\r\n     * Params:\r\n     *     backlog = Request of how many pending incoming connections are\r\n     *               queued until $(D_PSYMBOL accept)ed.\r\n     */\r\n    void listen(int backlog) const @trusted\r\n    {\r\n        if (.listen(handle_, backlog) == SOCKET_ERROR)\r\n        {\r\n            throw defaultAllocator.make!SocketException(\"Unable to listen on socket\");\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Compare handles.\r\n     *\r\n     * Params:\r\n     *     that = Another handle.\r\n     *\r\n     * Returns: Comparision result.\r\n     */\r\n    int opCmp(size_t that) const pure nothrow @safe @nogc\r\n    {\r\n        return handle_ < that ? -1 : handle_ > that ? 1 : 0;\r\n    }\r\n}\r\n\r\n/**\r\n * Interface with common fileds for stream and connected sockets.\r\n */\r\ninterface ConnectionOrientedSocket\r\n{\r\n    /**\r\n     * Flags may be OR'ed together.\r\n     */\r\n    enum Flag : int\r\n    {\r\n        none      = 0,             /// No flags specified\r\n        outOfBand = MSG_OOB,       /// Out-of-band stream data\r\n        peek      = MSG_PEEK,      /// Peek at incoming data without removing it from the queue, only for receiving\r\n        dontRoute = MSG_DONTROUTE, /// Data should not be subject to routing; this flag may be ignored. Only for sending\r\n    }\r\n\r\n    alias Flags = BitFlags!Flag;\r\n}\r\n\r\nclass StreamSocket : Socket, ConnectionOrientedSocket\r\n{\r\n    /**\r\n    * Create a socket.\r\n    *\r\n    * Params:\r\n    *     af       = Address family.\r\n    */\r\n    this(AddressFamily af) @trusted\r\n    {\r\n        auto handle = cast(socket_t) socket(af, SOCK_STREAM, 0);\r\n        if (handle == socket_t.init)\r\n        {\r\n            throw defaultAllocator.make!SocketException(\"Unable to create socket\");\r\n        }\r\n        super(handle, af);\r\n    }\r\n\r\n    /**\r\n     * Associate a local address with this socket.\r\n     *\r\n     * Params:\r\n     *     address = Local address.\r\n     *\r\n     * Throws: $(D_PSYMBOL SocketException) if unable to bind.\r\n     */\r\n    void bind(Address address) @trusted const\r\n    {\r\n        if (.bind(handle_, address.name, address.length) == SOCKET_ERROR)\r\n        {\r\n            throw defaultAllocator.make!SocketException(\"Unable to bind socket\");\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Accept an incoming connection.\r\n     *\r\n     * The blocking mode is always inherited.\r\n     *\r\n     * Returns: $(D_PSYMBOL Socket) for the accepted connection or\r\n     *          $(D_KEYWORD null) if the call would block on a\r\n     *          non-blocking socket.\r\n     *\r\n     * Throws: $(D_PSYMBOL SocketException) if unable to accept.\r\n     */\r\n    ConnectedSocket accept() @trusted\r\n    {\r\n        socket_t sock;\r\n\r\n        version (linux)\r\n        {\r\n            int flags;\r\n            if (!blocking)\r\n            {\r\n                flags |= SOCK_NONBLOCK;\r\n            }\r\n            sock = cast(socket_t).accept4(handle_, null, null, flags);\r\n        }\r\n        else\r\n        {\r\n            sock = cast(socket_t).accept(handle_, null, null);\r\n        }\r\n\r\n        if (sock == socket_t.init)\r\n        {\r\n            if (wouldHaveBlocked())\r\n            {\r\n                return null;\r\n            }\r\n            throw defaultAllocator.make!SocketException(\"Unable to accept socket connection\");\r\n        }\r\n\r\n        auto newSocket = defaultAllocator.make!ConnectedSocket(sock, addressFamily);\r\n\r\n        version (linux)\r\n        { // Blocking mode already set\r\n        }\r\n        else version (Posix)\r\n        {\r\n            if (!blocking)\r\n            {\r\n                try\r\n                {\r\n                    newSocket.blocking = blocking;\r\n                }\r\n                catch (SocketException e)\r\n                {\r\n                    defaultAllocator.dispose(newSocket);\r\n                    throw e;\r\n                }\r\n            }\r\n        }\r\n        else version (Windows)\r\n        { // Inherits blocking mode\r\n            newSocket.blocking_ = blocking;\r\n        }\r\n        return newSocket;\r\n    }\r\n}\r\n\r\n/**\r\n * Socket returned if a connection has been established.\r\n */\r\nclass ConnectedSocket : Socket, ConnectionOrientedSocket\r\n{\r\n    /**\r\n     * $(D_KEYWORD true) if the stream socket peer has performed an orderly\r\n     * shutdown.\r\n     */\r\n    protected bool disconnected_;\r\n\r\n    /**\r\n     * Returns: $(D_KEYWORD true) if the stream socket peer has performed an orderly\r\n     *          shutdown.\r\n     */\r\n    @property inout(bool) disconnected() inout const pure nothrow @safe @nogc\r\n    {\r\n        return disconnected_;\r\n    }\r\n\r\n    /**\r\n     * Create a socket.\r\n     *\r\n     * Params:\r\n     *     handle   = Socket.\r\n     *     af       = Address family.\r\n     */\r\n    this(socket_t handle, AddressFamily af)\r\n    {\r\n        super(handle, af);\r\n    }\r\n\r\n    version (Windows)\r\n    {\r\n        private static int capToMaxBuffer(size_t size) pure nothrow @safe @nogc\r\n        {\r\n            // Windows uses int instead of size_t for length arguments.\r\n            // Luckily, the send/recv functions make no guarantee that\r\n            // all the data is sent, so we use that to send at most\r\n            // int.max bytes.\r\n            return size > size_t (int.max) ? int.max : cast(int) size;\r\n        }\r\n    }\r\n    else\r\n    {\r\n        private static size_t capToMaxBuffer(size_t size) pure nothrow @safe @nogc\r\n        {\r\n            return size;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Receive data on the connection.\r\n     *\r\n     * Params:\r\n     *     buf =   Buffer to save received data.\r\n     *     flags = Flags.\r\n     *\r\n     * Returns: The number of bytes received or 0 if nothing received\r\n     *          because the call would block.\r\n     *\r\n     * Throws: $(D_PSYMBOL SocketException) if unable to receive.\r\n     */\r\n    ptrdiff_t receive(ubyte[] buf, Flags flags = Flag.none) @trusted\r\n    {\r\n        ptrdiff_t ret;\r\n        if (!buf.length)\r\n        {\r\n            return 0;\r\n        }\r\n\r\n        ret = recv(handle_, buf.ptr, capToMaxBuffer(buf.length), cast(int) flags);\r\n        if (ret == 0)\r\n        {\r\n            disconnected_ = true;\r\n        }\r\n        else if (ret == SOCKET_ERROR)\r\n        {\r\n            if (wouldHaveBlocked())\r\n            {\r\n                return 0;\r\n            }\r\n            disconnected_ = true;\r\n            throw defaultAllocator.make!SocketException(\"Unable to receive\");\r\n        }\r\n        return ret;\r\n    }\r\n\r\n    /**\r\n     * Send data on the connection. If the socket is blocking and there is no\r\n     * buffer space left, $(D_PSYMBOL send) waits, non-blocking socket returns\r\n     * 0 in this case.\r\n     *\r\n     * Params:\r\n     *     buf   = Data to be sent.\r\n     *     flags = Flags.\r\n     *\r\n     * Returns: The number of bytes actually sent.\r\n     *\r\n     * Throws: $(D_PSYMBOL SocketException) if unable to send.\r\n     */\r\n    ptrdiff_t send(const(ubyte)[] buf, Flags flags = Flag.none) const @trusted\r\n    {\r\n        int sendFlags = cast(int) flags;\r\n        ptrdiff_t sent;\r\n\r\n        static if (is(typeof(MSG_NOSIGNAL)))\r\n        {\r\n            sendFlags |= MSG_NOSIGNAL;\r\n        }\r\n\r\n        sent = .send(handle_, buf.ptr, capToMaxBuffer(buf.length), sendFlags);\r\n        if (sent != SOCKET_ERROR)\r\n        {\r\n            return sent;\r\n        }\r\n        else if (wouldHaveBlocked())\r\n        {\r\n            return 0;\r\n        }\r\n        throw defaultAllocator.make!SocketException(\"Unable to send\");\r\n    }\r\n}\r\n\r\n/**\r\n * Socket address representation.\r\n */\r\nabstract class Address\r\n{\r\n    /**\r\n     * Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure.\r\n     */\r\n    abstract @property inout(sockaddr)* name() inout pure nothrow @nogc;\r\n\r\n    /**\r\n     * Returns: Actual size of underlying $(D_PSYMBOL sockaddr) structure.\r\n     */\r\n    abstract @property inout(socklen_t) length() inout const pure nothrow @nogc;\r\n}\r\n\r\nclass InternetAddress : Address\r\n{\r\n    version (Windows)\r\n    {\r\n        /// Internal internet address representation.\r\n        protected SOCKADDR_STORAGE storage;\r\n    }\r\n    else version (Posix)\r\n    {\r\n        /// Internal internet address representation.\r\n        protected sockaddr_storage storage;\r\n    }\r\n    immutable ushort port_;\r\n\r\n    enum\r\n    {\r\n        anyPort = 0,\r\n    }\r\n\r\n    this(in string host, ushort port = anyPort)\r\n    {\r\n        if (getaddrinfoPointer is null || freeaddrinfoPointer is null)\r\n        {\r\n            throw defaultAllocator.make!AddressException(\"Address info lookup is not available on this system\");\r\n        }\r\n        addrinfo* ai_res;\r\n        port_ = port;\r\n\r\n        // Make C-string from host.\r\n        char[] node = defaultAllocator.makeArray!char(host.length + 1);\r\n        node[0.. $ - 1] = host;\r\n        node[$ - 1] = '\\0';\r\n        scope (exit)\r\n        {\r\n            defaultAllocator.dispose(node);\r\n        }\r\n\r\n        // Convert port to a C-string.\r\n        char[6] service = [0, 0, 0, 0, 0, 0];\r\n        const(char)* servicePointer;\r\n        if (port)\r\n        {\r\n            ushort start;\r\n            for (ushort j = 10, i = 4; i > 0; j *= 10, --i)\r\n            {\r\n                ushort rest = port % 10;\r\n                if (rest != 0)\r\n                {\r\n                    service[i] = cast(char) (rest + '0');\r\n                    start = i;\r\n                }\r\n                port /= 10;\r\n            }\r\n            servicePointer = service[start..$].ptr;\r\n        }\r\n\r\n        auto ret = getaddrinfoPointer(node.ptr, servicePointer, null, &ai_res);\r\n        if (ret)\r\n        {\r\n            throw defaultAllocator.make!AddressException(\"Address info lookup failed\");\r\n        }\r\n        scope (exit)\r\n        {\r\n            freeaddrinfoPointer(ai_res);\r\n        }\r\n\r\n        ubyte* dp = cast(ubyte*) &storage, sp = cast(ubyte*) ai_res.ai_addr;\r\n        for (auto i = ai_res.ai_addrlen; i > 0; --i, dp++, sp++)\r\n        {\r\n            *dp = *sp;\r\n        }\r\n        if (ai_res.ai_family != AddressFamily.INET && ai_res.ai_family != AddressFamily.INET6)\r\n        {\r\n            throw defaultAllocator.make!AddressException(\"Wrong address family\");\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure.\r\n     */\r\n    override @property inout(sockaddr)* name() inout pure nothrow @nogc\r\n    {\r\n        return cast(sockaddr*) &storage;\r\n    }\r\n\r\n    /**\r\n     * Returns: Actual size of underlying $(D_PSYMBOL sockaddr) structure.\r\n     */\r\n    override @property inout(socklen_t) length() inout const pure nothrow @nogc\r\n    {\r\n        // FreeBSD wants to know the exact length of the address on bind.\r\n        switch (family)\r\n        {\r\n            case AddressFamily.INET:\r\n                return sockaddr_in.sizeof;\r\n            case AddressFamily.INET6:\r\n                return sockaddr_in6.sizeof;\r\n            default:\r\n                assert(false);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns: Family of this address.\r\n     */\r\n    @property inout(AddressFamily) family() inout const pure nothrow @nogc\r\n    {\r\n        return cast(AddressFamily) storage.ss_family;\r\n    }\r\n\r\n    @property inout(ushort) port() inout const pure nothrow @nogc\r\n    {\r\n        return port_;\r\n    }\r\n}\r\n\r\n/**\r\n * Checks if the last error is a serious error or just a special\r\n * behaviour error of non-blocking sockets (for example an error\r\n * returned because the socket would block or because the\r\n * asynchronous operation was successfully started but not finished yet).\r\n *\r\n * Returns: $(D_KEYWORD false) if a serious error happened, $(D_KEYWORD true)\r\n *          otherwise.\r\n */\r\nbool wouldHaveBlocked() nothrow @trusted @nogc\r\n{\r\n    version (Posix)\r\n    {\r\n        return err.errno == err.EAGAIN || err.errno == err.EWOULDBLOCK;\r\n    }\r\n    else version (Windows)\r\n    {\r\n        return WSAGetLastError() == ERROR_IO_PENDING || WSAGetLastError() == err.EWOULDBLOCK\r\n            || WSAGetLastError() == ERROR_IO_INCOMPLETE;\r\n    }\r\n}\r\n\r\n/**\r\n * Returns: Platform specific error code.\r\n */\r\nprivate @property int lastError() nothrow @safe @nogc\r\n{\r\n    version (Windows)\r\n    {\r\n        return WSAGetLastError();\r\n    }\r\n    else\r\n    {\r\n        return err.errno;\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/network/url.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Eugene Wissner\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * URL parser\r\n *\r\n * Copyright: Eugene Wissner 2016-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Eugene Wissner\r\n */\r\nmodule dlib.network.url;\r\n\r\nimport std.ascii: isAlphaNum, isDigit;\r\nimport std.traits: isSomeString;\r\nimport std.uni: isAlpha, isNumber;\r\nimport std.uri;\r\n\r\nversion (unittest) private\r\n{\r\n    import std.typecons;\r\n    static Tuple!(string, string[string], ushort)[] URLTests;\r\n}\r\n\r\nstatic this()\r\n{\r\n    version (unittest)\r\n    {\r\n        URLTests = [\r\n            tuple(`127.0.0.1`, [\r\n                      \"path\": \"127.0.0.1\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://127.0.0.1`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"127.0.0.1\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://127.0.0.1/`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"127.0.0.1\",\r\n                      \"path\": \"/\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`127.0.0.1/`, [\r\n                      \"path\": \"127.0.0.1/\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`127.0.0.1:60000/`, [\r\n                      \"host\": \"127.0.0.1\",\r\n                      \"path\": \"/\",\r\n                  ], ushort(60000)),\r\n\r\n            tuple(`example.org`, [\r\n                      \"path\": \"example.org\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`example.org/`, [\r\n                      \"path\": \"example.org/\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://example.org`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"example.org\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://example.org/`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"example.org\",\r\n                      \"path\": \"/\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`www.example.org`, [\r\n                      \"path\": \"www.example.org\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`www.example.org/`, [\r\n                      \"path\": \"www.example.org/\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://www.example.org`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://www.example.org/`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`www.example.org:2`, [\r\n                      \"host\": \"www.example.org\",\r\n                  ], ushort(2)),\r\n\r\n            tuple(`http://www.example.org:80`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org:80/`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org/index.html`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/index.html\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`www.example.org/?`, [\r\n                      \"path\": \"www.example.org/\",\r\n                    \"query\": \"\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`www.example.org:80/?`, [\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/\",\r\n                    \"query\": \"\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org/?`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/\",\r\n                    \"query\": \"\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://www.example.org:80/?`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/\",\r\n                    \"query\": \"\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org:80/index.html`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/index.html\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org:80/foo/bar/index.html`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/foo/bar/index.html\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org:80/this/is/a/very/deep/directory/structure/and/file.png`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/this/is/a/very/deep/directory/structure/and/file.png\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org:80/deep/directory/structure/and/file.png?lots=1&of=2&parameters=3&too=4`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/deep/directory/structure/and/file.png\",\r\n                      \"query\": \"lots=1&of=2&parameters=3&too=4\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org:80/this/is/a/very/deep/directory/structure/and/`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/this/is/a/very/deep/directory/structure/and/\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org:80/this/is/a/very/deep/directory/structure/and/file.php`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/this/is/a/very/deep/directory/structure/and/file.php\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org:80/this/../a/../deep/directory`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/this/../a/../deep/directory\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org:80/this/../a/../deep/directory/`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/this/../a/../deep/directory/\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org:80/this/is/a/very/deep/directory/../image.png`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/this/is/a/very/deep/directory/../image.png\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org:80/index.html`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/index.html\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org:80/index.html?`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/index.html\",\r\n                    \"query\": \"\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org:80/#foo`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/\",\r\n                      \"fragment\": \"foo\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org:80/?#`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/\",\r\n                    \"query\": \"\",\r\n                    \"fragment\": \"\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org:80/?test=1`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/\",\r\n                      \"query\": \"test=1\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org/?test=1&`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/\",\r\n                      \"query\": \"test=1&\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://www.example.org:80/?&`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/\",\r\n                      \"query\": \"&\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org:80/index.html?test=1&`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/index.html\",\r\n                      \"query\": \"test=1&\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org/index.html?&`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/index.html\",\r\n                      \"query\": \"&\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://www.example.org:80/index.html?foo&`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/index.html\",\r\n                      \"query\": \"foo&\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://www.example.org/index.html?&foo`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/index.html\",\r\n                      \"query\": \"&foo\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://www.example.org:80/index.html?test=1&test2=char`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/index.html\",\r\n                      \"query\": \"test=1&test2=char\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`www.example.org:80/index.html?test=1&test2=char#some_ref123`, [\r\n                      \"host\": \"www.example.org\",\r\n                      \"path\": \"/index.html\",\r\n                      \"query\": \"test=1&test2=char\",\r\n                      \"fragment\": \"some_ref123\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://secret@www.example.org:80/index.html?test=1&test2=char#some_ref123`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"user\": \"secret\",\r\n                      \"path\": \"/index.html\",\r\n                      \"query\": \"test=1&test2=char\",\r\n                      \"fragment\": \"some_ref123\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://secret:@www.example.org/index.html?test=1&test2=char#some_ref123`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"user\": \"secret\",\r\n                      \"pass\": \"\",\r\n                      \"path\": \"/index.html\",\r\n                      \"query\": \"test=1&test2=char\",\r\n                      \"fragment\": \"some_ref123\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://:hideout@www.example.org:80/index.html?test=1&test2=char#some_ref123`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"user\": \"\",\r\n                      \"pass\": \"hideout\",\r\n                      \"path\": \"/index.html\",\r\n                      \"query\": \"test=1&test2=char\",\r\n                      \"fragment\": \"some_ref123\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`http://secret:hideout@www.example.org/index.html?test=1&test2=char#some_ref123`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"user\": \"secret\",\r\n                      \"pass\": \"hideout\",\r\n                      \"path\": \"/index.html\",\r\n                      \"query\": \"test=1&test2=char\",\r\n                      \"fragment\": \"some_ref123\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://secret:hid:out@www.example.org:80/index.html?test=1&test2=int#some_ref123`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.org\",\r\n                      \"user\": \"secret\",\r\n                      \"pass\": \"hid:out\",\r\n                      \"path\": \"/index.html\",\r\n                      \"query\": \"test=1&test2=int\",\r\n                      \"fragment\": \"some_ref123\",\r\n                  ], ushort(80)),\r\n\r\n            tuple(`nntp://news.example.org`, [\r\n                      \"scheme\": \"nntp\",\r\n                      \"host\": \"news.example.org\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`ftp://ftp.gnu.org/gnu/glic/glibc.tar.gz`, [\r\n                      \"scheme\": \"ftp\",\r\n                      \"host\": \"ftp.gnu.org\",\r\n                      \"path\": \"/gnu/glic/glibc.tar.gz\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`zlib:http://foo@bar`, [\r\n                      \"scheme\": \"zlib\",\r\n                      \"path\": \"http://foo@bar\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`zlib:filename.txt`, [\r\n                      \"scheme\": \"zlib\",\r\n                      \"path\": \"filename.txt\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`zlib:/path/to/my/file/file.txt`, [\r\n                      \"scheme\": \"zlib\",\r\n                      \"path\": \"/path/to/my/file/file.txt\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`foo://foo@bar`, [\r\n                      \"scheme\": \"foo\",\r\n                      \"host\": \"bar\",\r\n                      \"user\": \"foo\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`mailto:me@mydomain.com`, [\r\n                      \"scheme\": \"mailto\",\r\n                      \"path\": \"me@mydomain.com\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`/foo.php?a=b&c=d`, [\r\n                      \"path\": \"/foo.php\",\r\n                      \"query\": \"a=b&c=d\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`foo.php?a=b&c=d`, [\r\n                      \"path\": \"foo.php\",\r\n                      \"query\": \"a=b&c=d\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://user:passwd@www.example.com:8080?bar=1&boom=0`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"www.example.com\",\r\n                      \"user\": \"user\",\r\n                      \"pass\": \"passwd\",\r\n                      \"query\": \"bar=1&boom=0\",\r\n                  ], ushort(8080)),\r\n\r\n            tuple(`file:///path/to/file`, [\r\n                      \"scheme\": \"file\",\r\n                      \"path\": \"/path/to/file\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`file://path/to/file`, [\r\n                      \"scheme\": \"file\",\r\n                      \"host\": \"path\",\r\n                      \"path\": \"/to/file\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`file:/path/to/file`, [\r\n                      \"scheme\": \"file\",\r\n                      \"path\": \"/path/to/file\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://1.2.3.4:/abc.asp?a=1&b=2`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"1.2.3.4\",\r\n                      \"path\": \"/abc.asp\",\r\n                      \"query\": \"a=1&b=2\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://foo.com#bar`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"foo.com\",\r\n                      \"fragment\": \"bar\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`scheme:`, [\r\n                      \"scheme\": \"scheme\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`foo+bar://baz@bang/bla`, [\r\n                      \"scheme\": \"foo+bar\",\r\n                      \"host\": \"bang\",\r\n                      \"user\": \"baz\",\r\n                      \"path\": \"/bla\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`gg:9130731`, [\r\n                      \"scheme\": \"gg\",\r\n                      \"path\": \"9130731\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://10.10.10.10/:80`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"10.10.10.10\",\r\n                      \"path\": \"/:80\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://x:?`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"x\",\r\n                    \"query\": \"\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`x:blah.com`, [\r\n                      \"scheme\": \"x\",\r\n                      \"path\": \"blah.com\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`x:/blah.com`, [\r\n                      \"scheme\": \"x\",\r\n                      \"path\": \"/blah.com\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://::?`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \":\",\r\n                    \"query\": \"\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://::#`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \":\",\r\n                    \"fragment\": \"\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://?:/`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"?\",\r\n                      \"path\": \"/\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://@?:/`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"?\",\r\n                      \"user\": \"\",\r\n                      \"path\": \"/\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`file:///:`, [\r\n                      \"scheme\": \"file\",\r\n                      \"path\": \"/:\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`file:///a:/`, [\r\n                      \"scheme\": \"file\",\r\n                      \"path\": \"a:/\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`file:///ab:/`, [\r\n                      \"scheme\": \"file\",\r\n                      \"path\": \"/ab:/\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`file:///a:/`, [\r\n                      \"scheme\": \"file\",\r\n                      \"path\": \"a:/\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`file:///@:/`, [\r\n                      \"scheme\": \"file\",\r\n                      \"path\": \"@:/\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`file:///:80/`, [\r\n                      \"scheme\": \"file\",\r\n                      \"path\": \"/:80/\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`[]`, [\r\n                      \"path\": \"[]\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://[x:80]/`, [\r\n                      \"scheme\": \"http\",\r\n                      \"host\": \"[x:80]\",\r\n                      \"path\": \"/\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(``, [\r\n                      \"path\": \"\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`/`, [\r\n                      \"path\": \"/\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`/rest/Users?filter={\"id\":\"789\"}`, [\r\n                      \"path\": \"/rest/Users\",\r\n                      \"query\": `filter={\"id\":\"789\"}`,\r\n                  ], ushort(0)),\r\n\r\n            tuple(`//example.org`, [\r\n                      \"host\": \"example.org\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`/standard/?fq=B:20001`, [\r\n                      \"path\": \"/standard/\",\r\n                      \"query\": \"fq=B:20001\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`/standard/?fq=B:200013`, [\r\n                      \"path\": \"/standard/\",\r\n                      \"query\": \"fq=B:200013\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`/standard/?fq=home:012345`, [\r\n                      \"path\": \"/standard/\",\r\n                      \"query\": \"fq=home:012345\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`/standard/?fq=home:01234`, [\r\n                      \"path\": \"/standard/\",\r\n                      \"query\": \"fq=home:01234\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://user:pass@host`, [\r\n                    \"scheme\": \"http\",\r\n                    \"host\": \"host\",\r\n                      \"user\": \"user\",\r\n                      \"pass\": \"pass\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`//user:pass@host`, [\r\n                    \"host\": \"host\",\r\n                      \"user\": \"user\",\r\n                      \"pass\": \"pass\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`//user@host`, [\r\n                    \"host\": \"host\",\r\n                      \"user\": \"user\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`//example.org:99/hey?a=b#c=d`, [\r\n                    \"host\": \"example.org\",\r\n                      \"path\": \"/hey\",\r\n                      \"query\": \"a=b\",\r\n                      \"fragment\": \"c=d\",\r\n                  ], ushort(99)),\r\n\r\n            tuple(`//example.org/hey?a=b#c=d`, [\r\n                    \"host\": \"example.org\",\r\n                      \"path\": \"/hey\",\r\n                      \"query\": \"a=b\",\r\n                      \"fragment\": \"c=d\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://example.org/some/path.cgi?t=1#fragment?data`, [\r\n                    \"scheme\": \"http\",\r\n                    \"host\": \"example.org\",\r\n                      \"path\": \"/some/path.cgi\",\r\n                      \"query\": \"t=1\",\r\n                      \"fragment\": \"fragment?data\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`http://example.org/some/path.cgi#fragment?data`, [\r\n                    \"scheme\": \"http\",\r\n                    \"host\": \"example.org\",\r\n                      \"path\": \"/some/path.cgi\",\r\n                      \"fragment\": \"fragment?data\",\r\n                  ], ushort(0)),\r\n\r\n            tuple(`x://::abc/?`, string[string].init, ushort(0)),\r\n            tuple(`http:///blah.com`, string[string].init, ushort(0)),\r\n            tuple(`http://:80`, string[string].init, ushort(0)),\r\n            tuple(`http://user@:80`, string[string].init, ushort(0)),\r\n            tuple(`http://user:pass@:80`, string[string].init, ushort(0)),\r\n            tuple(`http://:`, string[string].init, ushort(0)),\r\n            tuple(`http://@/`, string[string].init, ushort(0)),\r\n            tuple(`http://@:/`, string[string].init, ushort(0)),\r\n            tuple(`http://:/`, string[string].init, ushort(0)),\r\n            tuple(`http://?`, string[string].init, ushort(0)),\r\n            tuple(`http://#`, string[string].init, ushort(0)),\r\n            tuple(`http://:?`, string[string].init, ushort(0)),\r\n            tuple(`http://blah.com:123456`, string[string].init, ushort(0)),\r\n            tuple(`http://blah.com:70000`, string[string].init, ushort(0)),\r\n            tuple(`http://blah.com:abcdef`, string[string].init, ushort(0)),\r\n            tuple(`http://secret@hideout@www.example.org:80/index.html?test=1&test2=char#some_ref123`,\r\n                  string[string].init,\r\n                  ushort(0)),\r\n            tuple(`http://user:@pass@host/path?argument?value#etc`, string[string].init, ushort(0)),\r\n            tuple(`http://foo.com\\@bar.com`, string[string].init, ushort(0)),\r\n            tuple(`http://email@address.com:pass@example.org`, string[string].init, ushort(0)),\r\n            tuple(`:`, string[string].init, ushort(0)),\r\n        ];\r\n    }\r\n}\r\n\r\n/**\r\n * A Unique Resource Locator.\r\n *\r\n * Params:\r\n *     U = URL string type.\r\n */\r\nstruct URL(U = string)\r\n    if (isSomeString!U)\r\n{\r\n    /** The URL scheme. */\r\n    U scheme;\r\n\r\n    /** The username. */\r\n    U user;\r\n\r\n    /** The password. */\r\n    U pass;\r\n\r\n    /** The hostname. */\r\n    U host;\r\n\r\n    /** The port number. */\r\n    ushort port;\r\n\r\n    /** The path. */\r\n    U path;\r\n\r\n    /** The query string. */\r\n    U query;\r\n\r\n    /** The anchor. */\r\n    U fragment;\r\n\r\n    /**\r\n     * Attempts to parse an URL from a string.\r\n     * Output string data (scheme, user, etc.) are just slices of input string (e.g., no memory allocation and copying).\r\n     *\r\n     * Params:\r\n     *  source = The string containing the URL.\r\n     *\r\n     * Throws: $(D_PSYMBOL URIException) if the URL is malformed.\r\n     */\r\n    this(U source)\r\n    {\r\n        auto value = source;\r\n        ptrdiff_t pos = -1, endPos = value.length, start;\r\n\r\n        foreach (i, ref c; source)\r\n        {\r\n            if (pos == -1 && c == ':')\r\n            {\r\n                pos = i;\r\n            }\r\n            if (endPos == value.length && (c == '?' || c == '#'))\r\n            {\r\n                endPos = i;\r\n            }\r\n        }\r\n\r\n        // Check if the colon is a part of the scheme or the port and parse\r\n        // the appropriate part\r\n        if (value.length > 1 && value[0] == '/' && value[1] == '/')\r\n        {\r\n            // Relative scheme\r\n            start = 2;\r\n        }\r\n        else if (pos > 0)\r\n        {\r\n            // Validate scheme\r\n            // [ toLower(alpha) | digit | \"+\" | \"-\" | \".\" ]\r\n            foreach (ref c; value[0..pos])\r\n            {\r\n                if (!c.isAlphaNum && c != '+' && c != '-' && c != '.')\r\n                {\r\n                    if (endPos > pos)\r\n                    {\r\n                        if (!parsePort(value[pos..$]))\r\n                        {\r\n                            throw new URIException(\"Failed to parse port\");\r\n                        }\r\n                    }\r\n                    goto ParsePath;\r\n                }\r\n            }\r\n\r\n            if (value.length == pos + 1) // only scheme is available\r\n            {\r\n                scheme = value[0 .. $ - 1];\r\n                return;\r\n            }\r\n            else if (value.length > pos + 1 && value[pos + 1] == '/')\r\n            {\r\n                scheme = value[0..pos];\r\n\r\n                if (value.length > pos + 2 && value[pos + 2] == '/')\r\n                {\r\n                    start = pos + 3;\r\n                    if (scheme == \"file\" && value.length > start && value[start] == '/')\r\n                    {\r\n                        // Windows drive letters\r\n                        if (value.length - start > 2 && value[start + 2] == ':')\r\n                        {\r\n                            ++start;\r\n                        }\r\n                        goto ParsePath;\r\n                    }\r\n                }\r\n                else\r\n                {\r\n                    start = pos + 1;\r\n                    goto ParsePath;\r\n                }\r\n            }\r\n            else // certain schemas like mailto: and zlib: may not have any / after them\r\n            {\r\n\r\n                if (!parsePort(value[pos..$]))\r\n                {\r\n                    scheme = value[0..pos];\r\n                    start = pos + 1;\r\n                    goto ParsePath;\r\n                }\r\n            }\r\n        }\r\n        else if (pos == 0 && parsePort(value[pos..$]))\r\n        {\r\n            // An URL shouldn't begin with a port number\r\n            throw new URIException(\"URL begins with port\");\r\n        }\r\n        else\r\n        {\r\n            goto ParsePath;\r\n        }\r\n\r\n        // Parse host\r\n        pos = -1;\r\n        for (ptrdiff_t i = start; i < value.length; ++i)\r\n        {\r\n            if (value[i] == '@')\r\n            {\r\n                pos = i;\r\n            }\r\n            else if (value[i] == '/')\r\n            {\r\n                endPos = i;\r\n                break;\r\n            }\r\n        }\r\n\r\n        // Check for login and password\r\n        if (pos != -1)\r\n        {\r\n            // *( unreserved / pct-encoded / sub-delims / \":\" )\r\n            foreach (i, c; value[start..pos])\r\n            {\r\n                if (c == ':')\r\n                {\r\n                    if (user is null)\r\n                    {\r\n                        user = value[start .. start + i];\r\n                        pass = value[start + i + 1 .. pos];\r\n                    }\r\n                }\r\n                else if (!c.isAlpha &&\r\n                         !c.isNumber &&\r\n                         c != '!' &&\r\n                         c != ';' &&\r\n                         c != '=' &&\r\n                         c != '_' &&\r\n                         c != '~' &&\r\n                         !(c >= '$' && c <= '.'))\r\n                {\r\n                    if (scheme !is null)\r\n                    {\r\n                        scheme = null;\r\n                    }\r\n                    if (user !is null)\r\n                    {\r\n                        user = null;\r\n                    }\r\n                    if (pass !is null)\r\n                    {\r\n                        pass = null;\r\n                    }\r\n                    throw new URIException(\"Restricted characters in user information\");\r\n                }\r\n            }\r\n            if (user is null)\r\n            {\r\n                user = value[start..pos];\r\n            }\r\n\r\n            start = ++pos;\r\n        }\r\n\r\n        pos = endPos;\r\n        if (endPos <= 1 || value[start] != '[' || value[endPos - 1] != ']')\r\n        {\r\n            // Short circuit portscan\r\n            // IPv6 embedded address\r\n            for (ptrdiff_t i = endPos - 1; i >= start; --i)\r\n            {\r\n                if (value[i] == ':')\r\n                {\r\n                    pos = i;\r\n                    if  (port == 0 && !parsePort(value[i..endPos]))\r\n                    {\r\n                        if (scheme !is null)\r\n                        {\r\n                            scheme = null;\r\n                        }\r\n                        if (user !is null)\r\n                        {\r\n                            user = null;\r\n                        }\r\n                        if (pass !is null)\r\n                        {\r\n                            pass = null;\r\n                        }\r\n                        throw new URIException(\"Invalid port\");\r\n                    }\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n\r\n        // Check if we have a valid host, if we don't reject the string as url\r\n        if (pos <= start)\r\n        {\r\n            if (scheme !is null)\r\n            {\r\n                scheme = null;\r\n            }\r\n            if (user !is null)\r\n            {\r\n                user = null;\r\n            }\r\n            if (pass !is null)\r\n            {\r\n                pass = null;\r\n            }\r\n            throw new URIException(\"Invalid host\");\r\n        }\r\n\r\n        host = value[start..pos];\r\n\r\n        if (endPos == value.length)\r\n        {\r\n            return;\r\n        }\r\n\r\n        start = endPos;\r\n\r\n    ParsePath:\r\n        endPos = value.length;\r\n        pos = -1;\r\n        foreach (i, ref c; value[start..$])\r\n        {\r\n            if (c == '?' && pos == -1)\r\n            {\r\n                pos = start + i;\r\n            }\r\n            else if (c == '#')\r\n            {\r\n                endPos = start + i;\r\n                break;\r\n            }\r\n        }\r\n        if (pos == -1)\r\n        {\r\n            pos = endPos;\r\n        }\r\n\r\n        if (pos > start)\r\n        {\r\n            path = value[start..pos];\r\n        }\r\n        if (endPos >= ++pos)\r\n        {\r\n            query = value[pos..endPos];\r\n        }\r\n        if (++endPos <= value.length)\r\n        {\r\n            fragment = value[endPos..$];\r\n        }\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        if (scheme !is null)\r\n        {\r\n            scheme = null;\r\n        }\r\n        if (user !is null)\r\n        {\r\n            user = null;\r\n        }\r\n        if (pass !is null)\r\n        {\r\n            pass = null;\r\n        }\r\n        if (host !is null)\r\n        {\r\n            host = null;\r\n        }\r\n        if (path !is null)\r\n        {\r\n            path = null;\r\n        }\r\n        if (query !is null)\r\n        {\r\n            query = null;\r\n        }\r\n        if (fragment !is null)\r\n        {\r\n            fragment = null;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Attempts to parse and set the port.\r\n     *\r\n     * Params:\r\n     *     port = String beginning with a colon followed by the port number and\r\n     *            an optional path (query string and/or fragment), like:\r\n     *            `:12345/some_path` or `:12345`.\r\n     *\r\n     * Returns: Whether the port could found.\r\n     */\r\n    private bool parsePort(U port) pure nothrow @safe @nogc\r\n    {\r\n        ptrdiff_t i = 1;\r\n        float lPort = 0;\r\n\r\n        for (; i < port.length && port[i].isDigit() && i <= 6; ++i)\r\n        {\r\n            lPort += (port[i] - '0') / cast(float)(10 ^^ (i - 1));\r\n        }\r\n        if (i == 1 && (i == port.length || port[i] == '/'))\r\n        {\r\n            return true;\r\n        }\r\n        else if (i == port.length || port[i] == '/')\r\n        {\r\n            lPort *= 10 ^^ (i - 2);\r\n            if (lPort > ushort.max)\r\n            {\r\n                return false;\r\n            }\r\n            this.port = cast(ushort)lPort;\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n}\r\n\r\nunittest\r\n{\r\n    auto u = URL!()(\"example.org\");\r\n    assert(u.path == \"example.org\");\r\n\r\n    u = URL!()(\"relative/path\");\r\n    assert(u.path == \"relative/path\");\r\n\r\n    // Host and scheme\r\n    u = URL!()(\"https://example.org\");\r\n    assert(u.scheme == \"https\");\r\n    assert(u.host == \"example.org\");\r\n    assert(u.path is null);\r\n    assert(u.port == 0);\r\n    assert(u.fragment is null);\r\n\r\n    // With user and port and path\r\n    u = URL!()(\"https://hilary:putnam@example.org:443/foo/bar\");\r\n    assert(u.scheme == \"https\");\r\n    assert(u.host == \"example.org\");\r\n    assert(u.path == \"/foo/bar\");\r\n    assert(u.port == 443);\r\n    assert(u.user == \"hilary\");\r\n    assert(u.pass == \"putnam\");\r\n    assert(u.fragment is null);\r\n\r\n    // With query string\r\n    u = URL!()(\"https://example.org/?login=true\");\r\n    assert(u.scheme == \"https\");\r\n    assert(u.host == \"example.org\");\r\n    assert(u.path == \"/\");\r\n    assert(u.query == \"login=true\");\r\n    assert(u.fragment is null);\r\n\r\n    // With query string and fragment\r\n    u = URL!()(\"https://example.org/?login=false#label\");\r\n    assert(u.scheme == \"https\");\r\n    assert(u.host == \"example.org\");\r\n    assert(u.path == \"/\");\r\n    assert(u.query == \"login=false\");\r\n    assert(u.fragment == \"label\");\r\n\r\n    u = URL!()(\"redis://root:password@localhost:2201/path?query=value#fragment\");\r\n    assert(u.scheme == \"redis\");\r\n    assert(u.user == \"root\");\r\n    assert(u.pass == \"password\");\r\n    assert(u.host == \"localhost\");\r\n    assert(u.port == 2201);\r\n    assert(u.path == \"/path\");\r\n    assert(u.query == \"query=value\");\r\n    assert(u.fragment == \"fragment\");\r\n}\r\n\r\nprivate unittest\r\n{\r\n    foreach(t; URLTests)\r\n    {\r\n        if (t[1].length == 0 && t[2] == 0)\r\n        {\r\n            try\r\n            {\r\n                URL!()(t[0]);\r\n                assert(0);\r\n            }\r\n            catch (URIException e)\r\n            {\r\n                assert(1);\r\n            }\r\n        }\r\n        else\r\n        {\r\n            auto u = URL!()(t[0]);\r\n            assert(\"scheme\" in t[1] ? u.scheme == t[1][\"scheme\"] : u.scheme is null,\r\n                   t[0]);\r\n            assert(\"user\" in t[1] ? u.user == t[1][\"user\"] : u.user is null, t[0]);\r\n            assert(\"pass\" in t[1] ? u.pass == t[1][\"pass\"] : u.pass is null, t[0]);\r\n            assert(\"host\" in t[1] ? u.host == t[1][\"host\"] : u.host is null, t[0]);\r\n            assert(u.port == t[2], t[0]);\r\n            assert(\"path\" in t[1] ? u.path == t[1][\"path\"] : u.path is null, t[0]);\r\n            assert(\"query\" in t[1] ? u.query == t[1][\"query\"] : u.query is null, t[0]);\r\n            if (\"fragment\" in t[1])\r\n            {\r\n                assert(u.fragment == t[1][\"fragment\"], t[0]);\r\n            }\r\n            else\r\n            {\r\n                assert(u.fragment is null, t[0]);\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/**\r\n * Contains possible URL components that can be returned from\r\n * $(D_PSYMBOL parseURL).\r\n */\r\nenum Component : string\r\n{\r\n    scheme = \"scheme\",\r\n    host = \"host\",\r\n    port = \"port\",\r\n    user = \"user\",\r\n    pass = \"pass\",\r\n    path = \"path\",\r\n    query = \"query\",\r\n    fragment = \"fragment\",\r\n}\r\n\r\n/**\r\n * Attempts to parse an URL from a string.\r\n *\r\n * Params:\r\n *     T      = $(D_SYMBOL Component) member or $(D_KEYWORD null) for a\r\n *              struct with all components.\r\n *     U      = URL string type.\r\n *     source = The string containing the URL.\r\n *\r\n * Returns: Requested URL component(s).\r\n */\r\nU parseURL(string T, U)(in U source)\r\n    if ((T == \"scheme\"\r\n      || T ==\"host\"\r\n      || T == \"user\"\r\n      || T == \"pass\"\r\n      || T == \"path\"\r\n      || T == \"query\"\r\n      || T == \"fragment\") && isSomeString!U)\r\n{\r\n    auto ret = URL!U(source);\r\n    return mixin(\"ret.\" ~ T);\r\n}\r\n\r\n/** ditto */\r\nauto parseURL(U)(in U source)\r\n    if (isSomeString!U)\r\n{\r\n    return URL!U(source);\r\n}\r\n\r\n/** ditto */\r\nushort parseURL(string T, U)(in U source)\r\n    if (T == \"port\" && isSomeString!U)\r\n{\r\n    auto ret = URL!U(source);\r\n    return ret.port;\r\n}\r\n\r\nunittest\r\n{\r\n    assert(parseURL!(Component.port)(\"http://example.org:5326\") == 5326);\r\n\r\n    immutable dstring url = \"http://example.org:5326\";\r\n    static assert(is(typeof(parseURL(url)) == URL!dstring));\r\n}\r\n\r\nprivate unittest\r\n{\r\n    foreach(t; URLTests)\r\n    {\r\n        if (t[1].length == 0 && t[2] == 0)\r\n        {\r\n            try\r\n            {\r\n                parseURL!(Component.port)(t[0]);\r\n                parseURL!(Component.user)(t[0]);\r\n                parseURL!(Component.pass)(t[0]);\r\n                parseURL!(Component.host)(t[0]);\r\n                parseURL!(Component.path)(t[0]);\r\n                parseURL!(Component.query)(t[0]);\r\n                parseURL!(Component.fragment)(t[0]);\r\n                assert(0);\r\n            }\r\n            catch (URIException e)\r\n            {\r\n                assert(1);\r\n            }\r\n        }\r\n        else\r\n        {\r\n            ushort port = parseURL!(Component.port)(t[0]);\r\n            string component = parseURL!(Component.scheme)(t[0]);\r\n            assert(\"scheme\" in t[1] ? component == t[1][\"scheme\"] : component is null,\r\n                   t[0]);\r\n            component = parseURL!(Component.user)(t[0]);\r\n            assert(\"user\" in t[1] ? component == t[1][\"user\"] : component is null,\r\n                   t[0]);\r\n            component = parseURL!(Component.pass)(t[0]);\r\n            assert(\"pass\" in t[1] ? component == t[1][\"pass\"] : component is null,\r\n                   t[0]);\r\n            component = parseURL!(Component.host)(t[0]);\r\n            assert(\"host\" in t[1] ? component == t[1][\"host\"] : component is null,\r\n                   t[0]);\r\n            assert(port == t[2], t[0]);\r\n            component = parseURL!(Component.path)(t[0]);\r\n            assert(\"path\" in t[1] ? component == t[1][\"path\"] : component is null,\r\n                   t[0]);\r\n            component = parseURL!(Component.query)(t[0]);\r\n            assert(\"query\" in t[1] ? component == t[1][\"query\"] : component is null,\r\n                   t[0]);\r\n            component = parseURL!(Component.fragment)(t[0]);\r\n            if (\"fragment\" in t[1])\r\n            {\r\n                assert(component == t[1][\"fragment\"], t[0]);\r\n            }\r\n            else\r\n            {\r\n                assert(component is null, t[0]);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "dlib/package.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * dlib - general purpose library\r\n *\r\n * Description:\r\n * dlib is a high-level general purpose library for \r\n * $(LINK2 https://dlang.org, D language) intended to \r\n * game engine developers. It provides basic building blocks for writing\r\n * graphics-intensive applications: containers, data streams, linear algebra \r\n * and image decoders.\r\n *\r\n * dlib has no external dependencies aside D's standard library. dlib is created\r\n * and maintained by $(LINK2 https://github.com/gecko0307, Timur Gafarov).\r\n *\r\n * If you like dlib, please support its development on\r\n * $(LINK2 https://www.patreon.com/gecko0307, Patreon) or\r\n * $(LINK2 https://liberapay.com/gecko0307, Liberapay). You can also make one-time \r\n * donation via $(LINK2 https://www.paypal.me/tgafarov, PayPal) or\r\n * $(LINK2 https://nowpayments.io/donation/gecko0307, NOWPayments).\r\n *\r\n * If you want to use dlib on macOS then, please, first read the \r\n * $(LINK2 https://github.com/gecko0307/dlib/wiki/Why-doesn't-dlib-support-macOS, manifesto).\r\n *\r\n * Currently dlib consists of the following packages:\r\n *\r\n * - $(LINK2 dlib/core.html, dlib.core) - basic functionality used by other modules (memory management, streams, threads, etc.)\r\n *\r\n * - $(LINK2 dlib/container.html, dlib.container) - generic data structures (GC-free dynamic and associative arrays and more)\r\n *\r\n * - $(LINK2 dlib/filesystem.html, dlib.filesystem) - abstract FS interface and its implementations for Windows and POSIX filesystems\r\n *\r\n * - $(LINK2 dlib/math.html, dlib.math) - linear algebra and numerical analysis (vectors, matrices, quaternions, linear system solvers, interpolation functions, etc.)\r\n *\r\n * - $(LINK2 dlib/geometry.html, dlib.geometry) - computational geometry (ray casting, primitives, intersection, etc.)\r\n *\r\n * - $(LINK2 dlib/image.html, dlib.image) - image processing (8-bit, 16-bit and 32-bit floating point channels, common filters and convolution kernels, resizing, FFT, HDRI, animation, graphics formats I/O: JPEG, PNG/APNG, BMP, TGA, HDR)\r\n *\r\n * - $(LINK2 dlib/audio.html, dlib.audio) - sound processing (8 and 16 bits per sample, synthesizers, WAV export and import)\r\n *\r\n * - $(LINK2 dlib/network.html, dlib.network) - networking and web functionality\r\n *\r\n * - $(LINK2 dlib/memory.html, dlib.memory) - memory allocators\r\n *\r\n * - $(LINK2 dlib/text.html, dlib.text) - text processing, GC-free strings, Unicode decoding and encoding\r\n *\r\n * - $(LINK2 dlib/random.html, dlib.random) - random number generation\r\n *\r\n * - $(LINK2 dlib/serialization.html, dlib.serialization) - data serialization (XML and JSON parsers)\r\n *\r\n * - $(LINK2 dlib/coding.html, dlib.coding)- various data compression and coding algorithms\r\n *\r\n * - $(LINK2 dlib/concurrency.html, dlib.concurrency) - a thread pool.\r\n *\r\n * Copyright: Timur Gafarov 2011-2025.\r\n * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib;\r\n\r\npublic\r\n{\r\n    import dlib.audio;\r\n    import dlib.coding;\r\n    import dlib.concurrency;\r\n    import dlib.container;\r\n    import dlib.core;\r\n    import dlib.filesystem;\r\n    import dlib.geometry;\r\n    import dlib.image;\r\n    import dlib.math;\r\n    import dlib.memory;\r\n    import dlib.network;\r\n    import dlib.random;\r\n    import dlib.serialization;\r\n    import dlib.text;\r\n}\r\n"
  },
  {
    "path": "dlib/random/package.d",
    "content": "/*\nCopyright (c) 2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\nmodule dlib.random;\n\npublic\n{\n    import dlib.random.random;\n}\n"
  },
  {
    "path": "dlib/random/random.d",
    "content": "/*\nCopyright (c) 2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Pseudo-random numbers based on C rand function\n *\n * Copyright: Timur Gafarov 2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.random.random;\n\nimport core.stdc.stdlib;\nimport core.stdc.time;\nimport core.thread.osthread: getpid;\nimport std.math;\nimport std.algorithm: sum;\n\nstatic this()\n{\n    srand(cast(uint)seed());\n}\n\nauto seed()\n{\n    return mix(clock(), time(null), getpid());\n}\n\n/**\n * Bob Jenkins' 96 bit mix function\n */\nulong mix(ulong a, ulong b, ulong c)\n{\n    a=a-b;  a=a-c;  a=a^(c >> 13);\n    b=b-c;  b=b-a;  b=b^(a << 8);\n    c=c-a;  c=c-b;  c=c^(b >> 13);\n    a=a-b;  a=a-c;  a=a^(c >> 12);\n    b=b-c;  b=b-a;  b=b^(a << 16);\n    c=c-a;  c=c-b;  c=c^(b >> 5);\n    a=a-b;  a=a-c;  a=a^(c >> 3);\n    b=b-c;  b=b-a;  b=b^(a << 10);\n    c=c-a;  c=c-b;  c=c^(b >> 15);\n    return c;\n}\n\n/**\n * Returns pseudo-random integer between mi (inclusive) and ma (exclusive)\n */\nint randomInRange(int mi, int ma)\n{\n    return (rand() % (ma - mi)) + mi;\n}\n\n/**\n * Returns pseudo-random floating-point number in 0..1 range\n */\nT random(T)()\n{\n    T res = (rand() % RAND_MAX) / cast(T)RAND_MAX;\n    return res;\n}\n"
  },
  {
    "path": "dlib/serialization/json.d",
    "content": "/*\nCopyright (c) 2018-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * JSON parser\n *\n * Copyright: Timur Gafarov 2018-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.serialization.json;\n\nimport std.stdio;\nimport std.conv;\nimport std.string;\nimport std.ascii;\n\nimport dlib.core.memory;\nimport dlib.core.compound;\nimport dlib.container.array;\nimport dlib.container.dict;\nimport dlib.text.utils;\nimport dlib.text.utf8;\nimport dlib.text.lexer;\nimport dlib.text.str;\n\nclass JSONLexer\n{\n    string text;\n    Lexer lexer;\n    string currentLexeme;\n    \n    enum delimiters = [\n        \"{\", \"}\", \"[\", \"]\", \",\", \":\", \"\\n\", \" \", \"\\\"\", \"\\'\", \"`\"\n    ];\n    \n    this(string text)\n    {\n        this.text = text;\n        lexer = New!Lexer(this.text, delimiters);\n        lexer.ignoreNewlines = true;\n        nextLexeme();\n    }\n\n    ~this()\n    {\n        Delete(lexer);\n    }\n    \n    void nextLexeme()\n    {\n        // Skip whitespaces\n        string lex;\n        do\n        {\n            lex = lexer.getLexeme();\n            if (lex.length == 0) break;\n        }\n        while (lex == \" \" || lex == \"\\t\" || lex == \"\\n\");\n        \n        // EOF\n        if (lex.length == 0)\n        {\n            currentLexeme = \"\";\n            return;\n        }\n\n        if (lex == \"\\\"\" || lex == \"\\'\" || lex == \"`\")\n        {\n            string quote = lex;\n            size_t startPos = lexer.position() - 1;\n\n            while (true)\n            {\n                auto nextLex = lexer.getLexeme();\n                if (nextLex.length == 0)\n                {\n                    // EOF without closing quote\n                    currentLexeme = text[startPos..$];\n                    return;\n                }\n                else if (nextLex.length == 1 && nextLex == quote)\n                {\n                    // Closing quote found\n                    size_t endPos = lexer.position();\n                    currentLexeme = text[startPos..endPos];\n                    return;\n                }\n            }\n        }\n        else\n        {\n            currentLexeme = lex;\n        }\n    }\n}\n\n/// JSON types enum\nenum JSONType\n{\n    Null,\n    Number,\n    String,\n    Array,\n    Object,\n    Boolean\n}\n\n/// JSON array\nalias JSONArray = Array!JSONValue;\n\n/// JSON object\nalias JSONObject = Dict!(JSONValue, string);\n\n/// JSON value\nclass JSONValue\n{\n    JSONType type;\n    double asNumber;\n    string asString;\n    JSONArray asArray;\n    JSONObject asObject;\n    bool asBoolean;\n\n    this()\n    {\n        asNumber = 0.0;\n        asString = \"\";\n        asObject = null;\n        asBoolean = false;\n    }\n\n    void addArrayElement(JSONValue element)\n    {\n        type = JSONType.Array;\n        asArray.append(element);\n    }\n\n    void addField(string name, JSONValue element)\n    {\n        if (asObject is null)\n            asObject = New!JSONObject();\n        type = JSONType.Object;\n        asObject[name] = element;\n    }\n\n    ~this()\n    {\n        if (asArray.length)\n        {\n            foreach(i, e; asArray.data)\n                Delete(e);\n            asArray.free();\n        }\n\n        if (asObject)\n        {\n            foreach(name, e; asObject)\n                Delete(e);\n            Delete(asObject);\n        }\n    }\n}\n\n/// JSON parsing result\nalias JSONResult = Compound!(bool, string);\n\n/// JSON parsing errors enum\nenum JSONError\n{\n    EOI = JSONResult(false, \"unexpected end of input\")\n}\n\n/// JSON document\nclass JSONDocument\n{\n    public:\n    bool isValid;\n    JSONValue root;\n\n    this(string input)\n    {\n        root = New!JSONValue();\n        root.type = JSONType.Object;\n        lexer = New!JSONLexer(input);\n        JSONResult res = parseValue(root);\n        isValid = res[0];\n        if (!isValid)\n            writeln(res[1]);\n    }\n\n    ~this()\n    {\n        Delete(root);\n        Delete(lexer);\n    }\n\n    protected:\n\n    JSONLexer lexer;\n    \n    string currentLexeme() @property\n    {\n        return lexer.currentLexeme;\n    }\n\n    void nextLexeme()\n    {\n        lexer.nextLexeme();\n    }\n\n    JSONResult parseValue(JSONValue value)\n    {\n        if (!currentLexeme.length)\n            return JSONError.EOI;\n\n        if (currentLexeme == \"{\")\n        {\n            nextLexeme();\n            while (currentLexeme.length && currentLexeme != \"}\")\n            {\n                string identifier = currentLexeme;\n                if (!identifier.length)\n                    return JSONError.EOI;\n                if (identifier[0] != '\\\"' || identifier[$-1] != '\\\"')\n                    return JSONResult(false, format(\"illegal identifier \\\"%s\\\"\", identifier));\n                identifier = identifier[1..$-1];\n\n                nextLexeme();\n                if (currentLexeme != \":\")\n                    return JSONResult(false, format(\"\\\":\\\" expected, got \\\"%s\\\"\", currentLexeme));\n\n                nextLexeme();\n                JSONValue newValue = New!JSONValue();\n                JSONResult res = parseValue(newValue);\n                if (!res[0])\n                    return res;\n\n                value.addField(identifier, newValue);\n\n                nextLexeme();\n\n                if (currentLexeme == \",\")\n                    nextLexeme();\n                else if (currentLexeme != \"}\")\n                    return JSONResult(false, format(\"\\\"}\\\" expected, got \\\"%s\\\"\", currentLexeme));\n            }\n        }\n        else if (currentLexeme == \"[\")\n        {\n            nextLexeme();\n            while (currentLexeme.length && currentLexeme != \"]\")\n            {\n                JSONValue newValue = New!JSONValue();\n                JSONResult res = parseValue(newValue);\n                if (!res[0])\n                    return res;\n\n                value.addArrayElement(newValue);\n\n                nextLexeme();\n\n                if (currentLexeme == \",\")\n                    nextLexeme();\n                else if (currentLexeme != \"]\")\n                    return JSONResult(false, format(\"\\\"}\\\" expected, got \\\"%s\\\"\", currentLexeme));\n            }\n        }\n        else\n        {\n            string data = currentLexeme;\n            if (data[0] == '\\\"')\n            {\n                if (data[$-1] != '\\\"')\n                    return JSONResult(false, format(\"illegal string \\\"%s\\\"\", data));\n                data = data[1..$-1];\n                value.type = JSONType.String;\n                value.asString = data;\n            }\n            else if (data == \"true\" || data == \"false\")\n            {\n                value.type = JSONType.Boolean;\n                value.asBoolean = data.to!bool;\n            }\n            else\n            {\n                value.type = JSONType.Number;\n                value.asNumber = data.to!double;\n            }\n        }\n\n        return JSONResult(true, \"\");\n    }\n}\n\n//\nunittest\n{\n    string input = \"\n    {\n        \\\"foo\\\": \\\"bar\\\",\n        \\\"test\\\": 100,\n        \\\"bool\\\": true,\n        \\\"arr\\\": [0, 1, 2]\n    }\n    \";\n    \n    JSONDocument doc = New!JSONDocument(input);\n    assert(doc.root.asObject[\"foo\"].asString == \"bar\");\n    assert(doc.root.asObject[\"test\"].asNumber == 100);\n    assert(doc.root.asObject[\"bool\"].asBoolean == true);\n    assert(doc.root.asObject[\"arr\"].asArray[1].asNumber == 1);\n    Delete(doc);\n}\n"
  },
  {
    "path": "dlib/serialization/package.d",
    "content": "/*\r\nCopyright (c) 2017-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Data serialization\r\n *\r\n * Copyright: Timur Gafarov 2017-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.serialization;\r\n\r\npublic\r\n{\r\n    import dlib.serialization.json;\r\n    import dlib.serialization.xml;\r\n}\r\n"
  },
  {
    "path": "dlib/serialization/xml.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * GC-free parser for a subset of XML.\r\n *\r\n * Description:\r\n * Has the following limitations:\r\n *\r\n * - supports only ASCII and UTF-8 encodings\r\n *\r\n * - doesn't support DOCTYPE and some other special tags\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.serialization.xml;\r\n\r\nimport std.stdio;\r\nimport std.conv;\r\nimport dlib.core.memory;\r\nimport dlib.core.compound;\r\nimport dlib.container.array;\r\nimport dlib.container.dict;\r\nimport dlib.container.stack;\r\nimport dlib.text.lexer;\r\nimport dlib.text.utils;\r\n\r\nstring[] xmlDelims =\r\n[\r\n    \"<\", \">\", \"</\", \"/>\", \"=\", \"<?\", \"?>\", \"\\\"\",\r\n    \"<!--\", \"-->\", \"<![CDATA[\", \"]]>\",\r\n    \"\\\"\", \"'\", \" \", \"\\n\",\r\n];\r\n\r\nenum XmlToken\r\n{\r\n    TagOpen,\r\n    TagClose,\r\n    TagName,\r\n    Assignment,\r\n    Quote,\r\n    PropValue\r\n}\r\n\r\nstring emptyStr;\r\n\r\nstring appendChar(string s, dchar ch)\r\n{\r\n    char[7] firstByteMark = [0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC];\r\n\r\n    char[4] chars;\r\n    uint byteMask = 0xBF;\r\n    uint byteMark = 0x80;\r\n\r\n    uint bytesToWrite = 0;\r\n    if (ch < 0x80) bytesToWrite = 1;\r\n    else if (ch < 0x800) bytesToWrite = 2;\r\n    else if (ch < 0x10000) bytesToWrite = 3;\r\n    else bytesToWrite = 4;\r\n\r\n    char* target = chars.ptr;\r\n    target += bytesToWrite;\r\n    switch (bytesToWrite)\r\n    {\r\n        case 4: *--target = cast(char)((ch | byteMark) & byteMask); ch >>= 6; goto case 3;\r\n        case 3: *--target = cast(char)((ch | byteMark) & byteMask); ch >>= 6; goto case 2;\r\n        case 2: *--target = cast(char)((ch | byteMark) & byteMask); ch >>= 6; goto case 1;\r\n        case 1: *--target = cast(char)(ch | firstByteMark[bytesToWrite]); break;\r\n        default: break;\r\n    }\r\n\r\n    return catStr(s, cast(string)chars[0..bytesToWrite]);\r\n}\r\n\r\nclass XmlNode\r\n{\r\n    XmlNode parent;\r\n    Array!XmlNode children;\r\n    string name;\r\n    string text;\r\n    Dict!(string, string) properties;\r\n\r\n    this(string name, XmlNode parent = null)\r\n    {\r\n        this.name = name;\r\n        this.parent = parent;\r\n        if (parent !is null)\r\n        {\r\n            parent.addChild(this);\r\n        }\r\n        this.properties = New!(Dict!(string, string));\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        if (text.length)\r\n            Delete(text);\r\n        if (name.length)\r\n            Delete(name);\r\n        foreach(k, v; properties)\r\n        {\r\n            Delete(k);\r\n            Delete(v);\r\n        }\r\n        Delete(properties);\r\n        foreach(c; children)\r\n        {\r\n            Delete(c);\r\n        }\r\n        children.free();\r\n    }\r\n\r\n    XmlNode firstChildByTag(string tag)\r\n    {\r\n        XmlNode res = null;\r\n        foreach(c; children)\r\n        {\r\n            if (c.name == tag)\r\n            {\r\n                res = c;\r\n                break;\r\n            }\r\n        }\r\n\r\n        return res;\r\n    }\r\n\r\n    void addChild(XmlNode node)\r\n    {\r\n        children.append(node);\r\n    }\r\n\r\n    void appendText(dchar c)\r\n    {\r\n        string newText = appendChar(text, c);\r\n        if (text.length)\r\n            Delete(text);\r\n        text = newText;\r\n    }\r\n\r\n    string getTextUnmanaged()\r\n    {\r\n        Array!char res;\r\n        res.append(text);\r\n        foreach(n; children)\r\n        {\r\n            string t = n.getTextUnmanaged();\r\n            if (t.length)\r\n            {\r\n                res.append(t);\r\n                Delete(t);\r\n            }\r\n        }\r\n        string output = immutableCopy(cast(string)res.data);\r\n        res.free();\r\n        return output;\r\n    }\r\n\r\n    void printProperties(dstring indent = \"\")\r\n    {\r\n        if (properties.length)\r\n        {\r\n            foreach(k, v; properties)\r\n                writeln(indent, k, \" = \", v);\r\n        }\r\n    }\r\n\r\n    // Warning! Causes GC allocation!\r\n    void print(dstring indent = \"\")\r\n    {\r\n        printProperties(indent);\r\n\r\n        foreach(n; children)\r\n        {\r\n            auto nm = n.name;\r\n            if (nm.length)\r\n                writeln(indent, \"tag: \", nm);\r\n            else\r\n                writeln(indent, \"tag: <anonymous>\");\r\n\r\n            string txt = n.getTextUnmanaged();\r\n            if (txt.length)\r\n            {\r\n                writeln(indent, \"text: \", txt);\r\n                Delete(txt);\r\n            }\r\n\r\n            n.print(indent ~ \" \");\r\n        }\r\n    }\r\n}\r\n\r\nstring prop(XmlNode node, string name)\r\n{\r\n    if (name in node.properties)\r\n        return node.properties[name];\r\n    else\r\n        return \"\";\r\n}\r\n\r\nclass XmlDocument\r\n{\r\n    XmlNode prolog = null;\r\n    XmlNode root;\r\n\r\n    this()\r\n    {\r\n        root = New!XmlNode(emptyStr);\r\n    }\r\n\r\n    ~this()\r\n    {\r\n        Delete(root);\r\n        if (prolog)\r\n            Delete(prolog);\r\n    }\r\n}\r\n\r\nXmlDocument parseXMLUnmanaged(string text)\r\n{\r\n    XmlDocument doc = New!XmlDocument();\r\n    Lexer lex = New!Lexer(text, xmlDelims);\r\n    Stack!XmlNode nodeStack;\r\n\r\n    nodeStack.push(doc.root);\r\n\r\n    XmlToken expect = XmlToken.TagOpen;\r\n\r\n    bool tagOpening = false;\r\n    bool xmlPrologDeclaration = false;\r\n    bool comment = false;\r\n    bool cdata = false;\r\n    bool lastCharWasWhitespace = false;\r\n\r\n    string tmpPropName;\r\n    Array!char tmpPropValue;\r\n\r\n    bool finished = false;\r\n\r\n    bool failed = false;\r\n    void error(string text, string t)\r\n    {\r\n        writefln(\"XML parse error: %s \\\"%s\\\"\", text, t);\r\n        failed = true;\r\n    }\r\n\r\n    string token;\r\n    while(!finished)\r\n    {\r\n        token = lex.getLexeme();\n\n        //writeln(token);\r\n\r\n        if (!token.length)\r\n            break;\n\r\n        //version(None)\r\n        switch(token)\r\n        {\r\n            case \"<![CDATA[\":\r\n                if (comment) break;\r\n                cdata = true;\r\n                break;\r\n\r\n            case \"]]>\":\r\n                if (comment) break;\r\n                if (cdata)\r\n                    cdata = false;\r\n                else\r\n                {\r\n                    error(\"Unexpected token \", token);\r\n                    finished = true;\r\n                }\r\n                break;\r\n\r\n            case \"<!--\":\r\n                if (cdata)\r\n                {\r\n                    XmlNode node = New!XmlNode(emptyStr, nodeStack.top);\r\n                    node.text = immutableCopy(token);\r\n                }\r\n                else\r\n                    comment = true;\r\n                break;\r\n\r\n            case \"-->\":\r\n                if (cdata)\r\n                {\r\n                    XmlNode node = New!XmlNode(emptyStr, nodeStack.top);\r\n                    node.text = immutableCopy(token);\r\n                }\r\n                else if (comment)\r\n                    comment = false;\r\n                else\r\n                {\r\n                    error(\"Unexpected token \", token);\r\n                    finished = true;\r\n                }\r\n                break;\r\n\r\n            case \"<\":\r\n                if (comment) break;\r\n                if (cdata)\r\n                {\r\n                    XmlNode node = New!XmlNode(emptyStr, nodeStack.top);\r\n                    node.text = immutableCopy(token);\r\n                }\r\n                else if (expect == XmlToken.TagOpen)\r\n                {\r\n                    expect = XmlToken.TagName;\r\n                    tagOpening = true;\r\n                }\r\n                else\r\n                {\r\n                    error(\"Unexpected token \", token);\r\n                    finished = true;\r\n                }\r\n                break;\r\n\r\n            case \">\":\r\n                if (comment) break;\r\n                if (cdata)\r\n                {\r\n                    XmlNode node = New!XmlNode(emptyStr, nodeStack.top);\r\n                    node.text = immutableCopy(token);\r\n                }\r\n                else if (expect == XmlToken.TagClose && !xmlPrologDeclaration)\r\n                {\r\n                    expect = XmlToken.TagOpen;\r\n                }\r\n                else\r\n                {\r\n                    error(\"Unexpected token \", token);\r\n                    finished = true;\r\n                }\r\n                break;\r\n\r\n            case \"</\":\r\n                if (comment) break;\r\n                if (cdata)\r\n                {\r\n                    XmlNode node = New!XmlNode(emptyStr, nodeStack.top);\r\n                    node.text = immutableCopy(token);\r\n                }\r\n                else if (expect == XmlToken.TagOpen)\r\n                {\r\n                    expect = XmlToken.TagName;\r\n                }\r\n                break;\r\n\r\n            case \"/>\":\r\n                if (comment) break;\r\n                if (cdata)\r\n                {\r\n                    XmlNode node = New!XmlNode(emptyStr, nodeStack.top);\r\n                    node.text = immutableCopy(token);\r\n                }\r\n                else if (expect == XmlToken.TagClose && !xmlPrologDeclaration)\r\n                {\r\n                    expect = XmlToken.TagOpen;\r\n                    nodeStack.pop();\r\n                }\r\n                else\r\n                {\r\n                    error(\"Unexpected token \", token);\r\n                    finished = true;\r\n                }\r\n                break;\r\n\r\n            case \"<?\":\r\n                if (comment) break;\r\n                if (cdata)\r\n                {\r\n                    XmlNode node = New!XmlNode(emptyStr, nodeStack.top);\r\n                    node.text = immutableCopy(token);\r\n                }\r\n                else if (expect == XmlToken.TagOpen)\r\n                {\r\n                    expect = XmlToken.TagName;\r\n                    xmlPrologDeclaration = true;\r\n                    tagOpening = true;\r\n                }\r\n                break;\r\n\r\n            case \"?>\":\r\n                if (comment) break;\r\n                if (cdata)\r\n                {\r\n                    XmlNode node = New!XmlNode(emptyStr, nodeStack.top);\r\n                    node.text = immutableCopy(token);\r\n                }\r\n                else if (expect == XmlToken.TagClose && xmlPrologDeclaration)\r\n                {\r\n                    expect = XmlToken.TagOpen;\r\n                    xmlPrologDeclaration = false;\r\n                    nodeStack.pop();\r\n                }\r\n                break;\r\n\r\n            case \"=\":\r\n                if (comment) break;\r\n                if (cdata)\r\n                {\r\n                    XmlNode node = New!XmlNode(emptyStr, nodeStack.top);\r\n                    node.text = immutableCopy(token);\r\n                }\r\n                else if (expect == XmlToken.Assignment)\r\n                {\r\n                    expect = XmlToken.Quote;\r\n                }\n                else if (expect == XmlToken.TagOpen)\r\n                {\r\n                    XmlNode node = New!XmlNode(emptyStr, nodeStack.top);\r\n                    node.text = immutableCopy(token);\n                }\r\n                else\r\n                {\r\n                    error(\"Unexpected token \", token);\r\n                    finished = true;\r\n                }\r\n                break;\r\n\r\n            case \"\\\"\":\r\n                if (comment) break;\r\n                if (cdata)\r\n                {\r\n                    XmlNode node = New!XmlNode(emptyStr, nodeStack.top);\r\n                    node.text = immutableCopy(token);\r\n                }\r\n                else if (expect == XmlToken.Quote)\r\n                {\r\n                    expect = XmlToken.PropValue;\r\n                }\r\n                else if (expect == XmlToken.PropValue)\r\n                {\r\n                    expect = XmlToken.TagClose;\r\n                    nodeStack.top.properties[immutableCopy(tmpPropName)] = immutableCopy(cast(string)tmpPropValue.data);\r\n                    tmpPropValue.free();\r\n                }\r\n                else\r\n                {\r\n                    error(\"Unexpected token \", token);\r\n                    finished = true;\r\n                }\r\n                break;\r\n\r\n            default:\r\n                if (comment) break;\r\n                if (cdata)\r\n                {\r\n                    XmlNode node = New!XmlNode(emptyStr, nodeStack.top);\r\n                    node.text = immutableCopy(token);\r\n                    break;\r\n                }\r\n\r\n                if (token != \" \" && token != \"\\n\")\r\n                    lastCharWasWhitespace = false;\r\n\r\n                if (token == \" \" || token == \"\\n\")\r\n                {\r\n                    if (expect == XmlToken.TagOpen)\r\n                    {\r\n                        if (nodeStack.top.children.length)\r\n                        {\r\n                            if (nodeStack.top.children.data[$-1].text == \" \")\r\n                                break;\r\n                        }\r\n                        else if (!nodeStack.top.text.length)\r\n                            break;\r\n                        else if (nodeStack.top.text[$-1] == ' ')\r\n                            break;\r\n\r\n                        XmlNode node = New!XmlNode(emptyStr, nodeStack.top);\r\n                        node.text = immutableCopy(\" \");\r\n                    }\r\n                    else if (expect == XmlToken.PropValue)\r\n                    {\r\n                        if (!lastCharWasWhitespace)\r\n                        {\r\n                            tmpPropValue.append(' ');\r\n                            lastCharWasWhitespace = true;\r\n                        }\r\n                    }\r\n                }\r\n                else if (expect == XmlToken.TagName)\r\n                {\r\n                    expect = XmlToken.TagClose;\r\n                    if (xmlPrologDeclaration)\r\n                    {\r\n                        if (tagOpening)\r\n                        {\r\n                            if (doc.prolog is null)\r\n                            {\r\n                                if (token == \"xml\")\r\n                                {\r\n                                    doc.prolog = New!XmlNode(immutableCopy(token));\r\n                                    nodeStack.push(doc.prolog);\r\n                                    tagOpening = false;\r\n                                }\r\n                                else\r\n                                {\r\n                                    error(\"Illegal XML prolog\", emptyStr);\r\n                                    finished = true;\r\n                                }\r\n                            }\r\n                            else\r\n                            {\r\n                                error(\"More than one XML prolog is not allowed\", emptyStr);\r\n                                finished = true;\r\n                            }\r\n                        }\r\n                        else\r\n                        {\r\n                            nodeStack.pop();\r\n                        }\r\n                    }\r\n                    else if (tagOpening)\r\n                    {\r\n                        XmlNode node = New!XmlNode(immutableCopy(token), nodeStack.top);\r\n                        nodeStack.push(node);\r\n                        tagOpening = false;\r\n                    }\r\n                    else\r\n                    {\r\n                        if (token == nodeStack.top.name)\r\n                            nodeStack.pop();\r\n                        else\r\n                        {\r\n                            error(\"Mismatched tag\", emptyStr);\r\n                            finished = true;\r\n                        }\r\n                    }\r\n                }\r\n                else if (expect == XmlToken.TagOpen)\r\n                {\r\n                    XmlNode node = New!XmlNode(emptyStr, nodeStack.top);\r\n                    if (token[0] == '&')\r\n                    {\r\n                        if (token[1] == '#' && token.length > 2)\r\n                        {\r\n                            dchar c = '?';\r\n                            if (token[2] == 'x')\r\n                            {\r\n                                int code = hexCharacterCode(token[3..$]);\r\n                                if (code == -1)\r\n                                {\r\n                                    error(\"Failed to parse character reference \", token);\r\n                                    finished = true;\r\n                                }\r\n                                else\r\n                                    c = cast(dchar)code;\r\n                            }\r\n                            else\r\n                                c = cast(dchar)to!uint(token[2..$-1]);\r\n\r\n                            node.appendText(c);\r\n                        }\n                        else\n                            node.text = immutableCopy(token);\r\n                    }\r\n                    else\r\n                        node.text = immutableCopy(token);\r\n                }\r\n                else if (expect == XmlToken.TagClose)\r\n                {\r\n                    expect = XmlToken.Assignment;\r\n\r\n                    if (tmpPropName.length)\r\n                        Delete(tmpPropName);\r\n                    tmpPropName = immutableCopy(token);\r\n                }\r\n                else if (expect == XmlToken.PropValue)\r\n                {\r\n                    tmpPropValue.append(token);\r\n                }\r\n                else\r\n                {\r\n                    error(\"Unexpected token \", token);\r\n                    finished = true;\r\n                }\r\n                break;\r\n        }\r\n    }\r\n\r\n    if (tmpPropName.length)\r\n        Delete(tmpPropName);\r\n    tmpPropValue.free();\r\n\r\n    nodeStack.free();\r\n    Delete(lex);\r\n\r\n    if (failed)\r\n    {\r\n        Delete(doc);\r\n        doc = null;\r\n    }\r\n\r\n    return doc;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    string xml = \"\r\n    <?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\r\n    <object>\r\n        <property name=\\\"position\\\" value=\\\"0 10 5\\\"/>\r\n        <!-- comment -->\r\n        <![CDATA[ some data ]]>\r\n        <foo>&#xE9;</foo>\r\n    </object>\r\n    \";\r\n    \r\n    XmlDocument doc = parseXMLUnmanaged(xml);\r\n    \r\n    assert(doc.prolog.properties[\"version\"] == \"1.0\");\r\n    assert(doc.prolog.properties[\"encoding\"] == \"UTF-8\");\r\n    \r\n    auto obj = doc.root.children[0];\r\n    assert(obj.name == \"object\");\r\n    \r\n    auto p = obj.children[0];\r\n    assert(p.name == \"property\");\r\n    assert(p.properties[\"name\"] == \"position\");\r\n    assert(p.properties[\"value\"] == \"0 10 5\");\r\n    \r\n    assert(prop(p, \"name\") == \"position\");\r\n    assert(prop(p, \"something\") == \"\");\r\n    \r\n    auto foo = obj.firstChildByTag(\"foo\");\r\n    assert(foo.name == \"foo\");\r\n    \r\n    string fooText = foo.getTextUnmanaged();\r\n    assert(fooText == \"é\");\r\n    Delete(fooText);\r\n    \r\n    Delete(doc);\r\n}\r\n\r\nint hexCharacterCode(string input)\r\n{\r\n    int res;\r\n    foreach(c; input)\r\n    {\r\n        switch(c)\r\n        {\r\n            case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':\r\n                res = res * 0x10 | c - '0';\r\n                break;\r\n            case 'a', 'b', 'c', 'd', 'e', 'f':\r\n                res = res * 0x10 | c - 'a' + 0xA;\r\n                break;\r\n            case 'A', 'B', 'C', 'D', 'E', 'F':\r\n                res = res * 0x10 | c - 'A' + 0xA;\r\n                break;\r\n            case ';':\r\n                return res;\r\n            default:\r\n                return -1;\r\n        }\r\n    }\r\n    return res;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    assert(hexCharacterCode(\"ff00ff\") == 16711935);\r\n    assert(hexCharacterCode(\"ABABAB;\") == 11250603);\r\n}\r\n"
  },
  {
    "path": "dlib/text/common.d",
    "content": "/*\nCopyright (c) 2018-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\nmodule dlib.text.common;\n\n/// Denotes an end of data\nenum DECODE_END = -1;\n\n/// Denotes an error while decoding\nenum DECODE_ERROR = -2;\n"
  },
  {
    "path": "dlib/text/encodings.d",
    "content": "/*\nCopyright (c) 2018-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Generic encoding tools\n *\n * Description:\n * This module works with any encoder and decoder structs that implement \n * the following basic interfaces:\n * ---\n * struct Encoder\n * {\n *     // Encodes a Unicode code point to user-provided buffer.\n *     // Should return bytes written or 0 at error\n *     size_t encode(uint codePoint, char[] buffer)\n * }\n *\n * struct Decoder\n * {\n *     // An input range that iterates characters of a string, \n *     // decoding them to Unicode code points\n *     auto decode(string input)\n * }\n * ---\n *\n * Copyright: Timur Gafarov 2018-2025.\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.text.encodings;\n\nimport dlib.container.array;\nimport dlib.text.utils;\n\npublic\n{\n    import dlib.text.utf8;\n    import dlib.text.utf16;\n}\n\n/**\n * Converts a string from one encoding to another.\n * Decoder and encoder are specified at compile time\n *\n * Examples:\n * ---\n * string s = transcode!(UTF16Decoder, UTF8Encoder)(input);\n * ---\n */\nstring transcode(Decoder, Encoder)(string input)\n{\n    DynamicArray!char array;\n    \n    auto decoder = Decoder();\n    auto encoder = Encoder();\n    \n    foreach(c; decoder.decode(input))\n    {\n        char[4] buffer;\n        size_t len = encoder.encode(c, buffer);\n        if (len)\n            array.append(buffer[0..len]);\n    }\n    \n    auto output = copy(array.data);\n    array.free();\n    return cast(string)output;\n}\n"
  },
  {
    "path": "dlib/text/lexer.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * General-purpose non-allocating lexical analyzer.\r\n *\r\n * Description:\r\n * Breaks the input string to a stream of lexemes according to a given delimiter dictionary.\r\n * Delimiters are symbols that separate sequences of characters (e.g. operators).\r\n * Lexemes are slices of the input string.\r\n * Assumes UTF-8 input.\r\n * Treats \\r\\n as a single \\n.\r\n *\r\n * Copyright: Timur Gafarov 2016-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov, Eugene Wissner, Roman Chistokhodov, ijet\r\n */\r\nmodule dlib.text.lexer;\r\n\r\nimport std.ascii;\r\nimport std.range.interfaces;\r\nimport dlib.text.utf8;\r\n\r\n/**\r\n * Lexical analyzer class\r\n */\r\nclass Lexer: InputRange!string\r\n{\r\n    protected:\r\n    string input;\r\n    string[] delims;\r\n\r\n    UTF8Decoder dec;\r\n    uint index;\r\n    dchar current;\r\n    uint currentSize;\r\n\r\n    public:\r\n    bool ignoreWhitespaces = false;\r\n    bool ignoreNewlines = false;\r\n\r\n    this(string input, string[] delims)\r\n    {\r\n        this.input = input;\r\n        this.delims = delims;\r\n        this.dec.input = this.input;\r\n        this.index = 0;\r\n        advance();\r\n    }\r\n    \r\n    uint position() @property\r\n    {\r\n        return pos();\r\n    }\r\n\r\n    string getLexeme()\r\n    {\r\n        string res = \"\";\r\n        int tStart = -1;\r\n        while(true)\r\n        {\r\n            dchar c = current;\r\n            if (c == UTF8_END || c == UTF8_ERROR)\r\n            {\r\n                if (tStart > -1)\r\n                {\r\n                    res = input[tStart..pos];\r\n                    tStart = -1;\r\n                    break;\r\n                }\r\n                else\r\n                {\r\n                    res = \"\";\r\n                    break;\r\n                }\r\n            }\r\n\r\n            if (isNewline(c)) \r\n            {\r\n                if (tStart > -1)\r\n                {\r\n                    res = input[tStart..pos];\r\n                    tStart = -1;\r\n                    break;\r\n                }\r\n                else\r\n                {\r\n                    if (c == '\\r')\r\n                    {\r\n                        advance();\r\n                        c = current;\r\n                        if (c == '\\n')\r\n                            advance();\r\n                    }\r\n                    else\r\n                    {\r\n                        advance();\r\n                    }\r\n\r\n                    if (ignoreNewlines)\r\n                    {\r\n                        continue;\r\n                    }\r\n                    else\r\n                    {\r\n                        res = \"\\n\";\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n\r\n            if (isWhitespace(c))\r\n            {\r\n                if (tStart > -1)\r\n                {\r\n                    res = input[tStart..pos];\r\n                    tStart = -1;\r\n                    break;\r\n                }\r\n                else\r\n                {\r\n                    advance();\r\n                    if (ignoreWhitespaces)\r\n                    {\r\n                        continue;\r\n                    }\r\n                    else\r\n                    {\r\n                        res = \" \";\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n\r\n            string d = consumeDelimiter();\r\n            if (d.length)\r\n            {\r\n                if (tStart > -1)\r\n                {\r\n                    res = input[tStart..pos];\r\n                }\r\n                else\r\n                {\r\n                    res = d;\r\n                    forwardJump(d.length);\r\n                }\r\n\r\n                break;\r\n            }\r\n            else\r\n            {\r\n                if (tStart == -1)\r\n                {\r\n                    tStart = pos;\r\n                }\r\n                advance();\r\n            }\r\n        }\r\n\r\n        return res;\r\n    }\r\n\r\n    protected:\r\n\r\n    uint pos()\r\n    {\r\n        return index-currentSize;\r\n    }\r\n\r\n    void advance()\r\n    {\r\n        current = dec.decodeNext();\r\n        currentSize = cast(uint)dec.index - index;\r\n        index += currentSize;\r\n    }\r\n\r\n    void forwardJump(size_t numChars)\r\n    {\r\n        for(size_t i = 0; i < numChars; i++)\r\n        {\r\n            advance();\r\n        }\r\n    }\r\n\r\n    bool forwardCompare(string str)\r\n    {\r\n        UTF8Decoder dec2 = UTF8Decoder(str);\r\n        size_t oldIndex = dec.index;\r\n\r\n        bool res = true;\r\n\r\n        int c1 = current;\r\n        int c2 = dec2.decodeNext();\r\n        do\r\n        {\r\n            if (c2 == UTF8_END || c2 == UTF8_ERROR)\r\n                break;\r\n\r\n            if (c1 == UTF8_END && c2 == UTF8_END)\r\n            {\r\n                res = true;\r\n                break;\r\n            }\r\n\r\n            if (c1 != UTF8_END && c1 != UTF8_ERROR &&\r\n                c2 != UTF8_END && c2 != UTF8_ERROR)\r\n            {\r\n                if (c1 != c2)\r\n                {\r\n                    res = false;\r\n                    break;\r\n                }\r\n            }\r\n            else\r\n            {\r\n                res = false;\r\n                break;\r\n            }\r\n\r\n            c1 = dec.decodeNext();\r\n            c2 = dec2.decodeNext();\r\n        }\r\n        while(c2 != UTF8_END && c2 != UTF8_ERROR);\r\n\r\n        dec.index = oldIndex;\r\n\r\n        return res;\r\n    }\r\n\r\n    bool isWhitespace(dchar c)\r\n    {\r\n        foreach(w; std.ascii.whitespace)\r\n        {\r\n            if (c == w)\r\n            {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    bool isNewline(dchar c)\r\n    {\r\n        return (c == '\\n' || c == '\\r');\r\n    }\r\n\r\n    string consumeDelimiter()\r\n    {\r\n        size_t bestLen = 0;\r\n        string bestStr = \"\";\r\n        foreach(d; delims)\r\n        {\r\n            if (forwardCompare(d))\r\n            {\r\n                if (d.length > bestLen)\r\n                {\r\n                    bestLen = d.length;\r\n                    bestStr = input[pos..pos+d.length];\r\n                }\r\n            }\r\n        }\r\n        return bestStr;\r\n    }\r\n\r\n    // Range interface\r\n\r\n    private:\r\n\r\n    string _front;\r\n\r\n    public:\r\n\r\n    bool empty()\r\n    {\r\n        return _front.length == 0;\r\n    }\r\n\r\n    string front()\r\n    {\r\n        return _front;\r\n    }\r\n\r\n    void popFront()\r\n    {\r\n        _front = getLexeme();\r\n    }\r\n\r\n    string moveFront()\r\n    {\r\n        _front = getLexeme();\r\n        return _front;\r\n    }\r\n\r\n    int opApply(scope int delegate(string) dg)\r\n    {\r\n        int result = 0;\r\n\r\n        while(true)\r\n        {\r\n            string lexeme = getLexeme();\r\n\r\n            if (!lexeme.length)\r\n                break;\r\n\r\n            result = dg(lexeme);\r\n            if (result)\r\n                break;\r\n        }\r\n\r\n        return result;\r\n    }\r\n\r\n    int opApply(scope int delegate(size_t, string) dg)\r\n    {\r\n        int result = 0;\r\n        size_t i = 0;\r\n\r\n        while(true)\r\n        {\r\n            string lexeme = getLexeme();\r\n\r\n            if (!lexeme.length)\r\n                break;\r\n\r\n            result = dg(i, lexeme);\r\n            if (result)\r\n                break;\r\n\r\n            i++;\r\n        }\r\n\r\n        return result;\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    import std.array: array;\r\n    import std.range: iota;\r\n    \r\n    string[] delims =\r\n    [\r\n        \"(\", \")\", \";\", \" \", \"{\", \"}\", \".\", \"\\n\", \"\\r\", \"=\", \"++\", \"<\"\r\n    ];\r\n    auto input = \"for (int i=0; i<arr.length; ++i)\\r\\n{doThing();}\\n\";\r\n    auto lexer = new Lexer(input, delims);\r\n\r\n    string[] arr;\r\n    while(true)\r\n    {\r\n        auto lexeme = lexer.getLexeme();\r\n        if(lexeme.length == 0)\r\n            break;\r\n        arr ~= lexeme;\r\n    }\r\n    auto reference =\r\n    [\r\n        \"for\", \" \", \"(\", \"int\", \" \", \"i\", \"=\", \"0\", \";\", \" \", \"i\", \"<\",\r\n        \"arr\", \".\", \"length\", \";\", \" \", \"++\", \"i\", \")\", \"\\n\", \"{\", \"doThing\",\r\n        \"(\", \")\", \";\", \"}\", \"\\n\"\r\n    ];\r\n    assert(arr == reference);\r\n\r\n    lexer = new Lexer(input, delims);\r\n    arr = array(lexer);\r\n    assert(arr == reference);\r\n\r\n    input = \"\";\r\n    lexer = new Lexer(input, delims);\r\n    assert(lexer.getLexeme().length == 0);\r\n}\r\n"
  },
  {
    "path": "dlib/text/package.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Text processing\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.text;\r\n\r\npublic\r\n{\r\n    import dlib.text.str;\r\n    import dlib.text.utils;\r\n    import dlib.text.utf8;\r\n    import dlib.text.utf16;\r\n    import dlib.text.encodings;\r\n    import dlib.text.lexer;\r\n}\r\n"
  },
  {
    "path": "dlib/text/str.d",
    "content": "/*\nCopyright (c) 2018-2025 Timur Gafarov\n\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * GC-free UTF-8 string type\n *\n * Copyright: Timur Gafarov 2018-2025.\n * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0).\n * Authors: Timur Gafarov\n */\nmodule dlib.text.str;\n\nimport dlib.core.memory;\nimport dlib.container.array;\nimport dlib.text.utf8;\nimport dlib.core.stream;\n\n/**\n * GC-free UTF-8 string type based on dlib.container.array.\n * Stores up to 128 bytes without dynamic memory allocation,\n * so short strings are processed very fast.\n * String is always zero-terminated and directly compatible with C.\n */\nstruct String\n{\n    /**\n     * Underlying array of characters\n     */\n    Array!(char, 128) data;\n\n    private void addZero()\n    {\n        data.insertBack('\\0');\n    }\n\n    private void removeZero()\n    {\n        data.removeBack(1);\n    }\n\n    /**\n     * Construct from D string\n     */\n    this(string s)\n    {\n        data.insertBack(s);\n        addZero();\n    }\n\n    /**\n     * Construct from zero-terminated C string (ASCII or UTF8)\n     */\n    this(const(char)* cStr)\n    {\n        size_t offset = 0;\n        while(cStr[offset] != 0)\n        {\n            offset++;\n        }\n        if (offset > 0)\n            data.insertBack(cStr[0..offset]);\n        addZero();\n    }\n\n    /**\n     * Construct from zero-terminated UTF-16 LE string\n     */\n    this(const(wchar)* wStr)\n    {\n        wchar* utf16 = cast(wchar*)wStr;\n        wchar utf16char;\n        do\n        {\n            utf16char = *wStr;\n            utf16++;\n\n            if (utf16char)\n            {\n                if (utf16char < 0x80)\n                {\n                    data.insertBack((utf16char >> 0 & 0x7F) | 0x00);\n                }\n                else if (utf16char < 0x0800)\n                {\n                    data.insertBack((utf16char >> 6 & 0x1F) | 0xC0);\n                    data.insertBack((utf16char >> 0 & 0x3F) | 0x80);\n                }\n                else if (utf16char < 0x010000)\n                {\n                    data.insertBack((utf16char >> 12 & 0x0F) | 0xE0);\n                    data.insertBack((utf16char >> 6 & 0x3F) | 0x80);\n                    data.insertBack((utf16char >> 0 & 0x3F) | 0x80);\n                }\n                else if (utf16char < 0x110000)\n                {\n                    data.insertBack((utf16char >> 18 & 0x07) | 0xF0);\n                    data.insertBack((utf16char >> 12 & 0x3F) | 0x80);\n                    data.insertBack((utf16char >> 6 & 0x3F) | 0x80);\n                    data.insertBack((utf16char >> 0 & 0x3F) | 0x80);\n                }\n            }\n        }\n        while(utf16char);\n        addZero();\n    }\n\n    /**\n     * Construct from an InputStream\n     */\n    this(InputStream istrm)\n    {\n        data.resize(cast(size_t)istrm.size, 0);\n        istrm.fillArray(data.data);\n        addZero();\n    }\n\n    void free()\n    {\n        data.free();\n    }\n\n    auto opOpAssign(string op)(string s) if (op == \"~\")\n    {\n        removeZero();\n        data.insertBack(s);\n        addZero();\n        return this;\n    }\n\n    auto opOpAssign(string op)(char c) if (op == \"~\")\n    {\n        removeZero();\n        data.insertBack(c);\n        addZero();\n        return this;\n    }\n\n    auto opOpAssign(string op)(String s) if (op == \"~\")\n    {\n        String s1 = this;\n        s1.removeZero();\n        s1 ~= s;\n        s1.addZero();\n        return s1;\n    }\n\n    void reserve(size_t amount)\n    {\n        data.reserve(amount);\n    }\n\n    @property size_t length()\n    {\n        if (data.length == 0)\n            return 0;\n        else\n            return data.length - 1;\n    }\n\n    @property string toString() const\n    {\n        if (data.length == 0)\n            return \"\";\n        else\n            return cast(string)data.readOnlyData[0..$-1];\n    }\n\n    alias toString this;\n\n    @property const(char)* ptr() const\n    {\n        return data.readOnlyData.ptr;\n    }\n\n    @property bool isDynamic()\n    {\n        return data.isDynamic;\n    }\n\n    /**\n     * Range interface that iterates the string by Unicode code point (dchar),\n     * i.e., foreach(dchar c; str.decode)\n     */\n    auto decode()\n    {\n        return UTF8Decoder().decode(toString());\n    }\n}\n\n///\nunittest\n{\n    String s = \"hello\";\n    s ~= \", world\";\n    s ~= '!';\n    assert(!s.isDynamic);\n    string dStr = s;\n    assert(dStr == \"hello, world!\");\n    s.free();\n    assert(s.length == 0);\n    \n    const(char)* cStr = \"Hello!\";\n    String s2 = String(cStr);\n    assert(s2.toString == \"Hello!\");\n    \n    import std.algorithm.comparison: equal;\n    assert(equal(s2.decode, ['H', 'e', 'l', 'l', 'o', '!']));\n    \n    auto istrm = new ArrayStream([104, 101, 108, 108, 111]);\n    String s3 = String(istrm);\n    assert(s3.toString == \"hello\");\n}\n"
  },
  {
    "path": "dlib/text/utf16.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * UTF-16 decoder and encoder\r\n *\r\n * Copyright: Timur Gafarov 2016-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov, Roman Chistokhodov\r\n */\r\nmodule dlib.text.utf16;\r\n\r\nimport core.stdc.stdio;\r\nimport dlib.core.memory;\r\nimport dlib.container.array;\r\nimport dlib.text.utf8;\r\nimport dlib.text.utils;\r\nimport dlib.text.common;\r\n\r\nenum ushort UTF16_HI_SURROGATE = 0xD800;\r\nenum ushort UTF16_LO_SURROGATE = 0xDC00;\r\nenum ushort UTF16_BOM_LE = 0xfeff;\r\nenum ushort UTF16_BOM_BE = 0xfffe;\r\n\r\n/**\r\n * UTF-16 LE decoder to use with dlib.text.encodings.transcode\r\n */\r\nstruct UTF16LEDecoder\r\n{\r\n    // TODO: byte order\r\n    public:\r\n\r\n    /// Input string. Set it before decoding\r\n    string input;\r\n\r\n    /// Current index in an input string\r\n    size_t index = 0;\r\n\r\n    /// Current character index\r\n    int character = 0;\r\n\r\n    /**\r\n     * Decode next character.\r\n     * Returns: decoded code point, or UTF8_ERROR if error occured, or UTF8_END if input has no more characters.\r\n     */\r\n    int decodeNext()\r\n    {\r\n        if (index >= input.length)\r\n            return index == input.length ? DECODE_END : DECODE_ERROR;\r\n        character++;\r\n        wchar c = *cast(wchar*)(&input[index]);\r\n        index += 2;\r\n        return c;\r\n    }\r\n\r\n    /**\r\n     * Check if decoder is in the end of input.\r\n     */\r\n    bool eos()\r\n    {\r\n        return (index >= input.length);\r\n    }\r\n\r\n    /**\r\n     * Range interface.\r\n     */\r\n    auto decode(string s)\r\n    {\r\n        input = s;\r\n\r\n        static struct ByDchar\r\n        {\r\n            private:\r\n            UTF16LEDecoder _decoder;\r\n            dchar _lastRead;\r\n\r\n            public:\r\n            this(UTF16LEDecoder decoder)\r\n            {\r\n                _decoder = decoder;\r\n                _lastRead = cast(dchar)_decoder.decodeNext();\r\n            }\r\n\r\n            bool empty()\r\n            {\r\n                return _lastRead == DECODE_END || _lastRead == DECODE_ERROR;\r\n            }\r\n\r\n            dchar front()\r\n            {\r\n                return _lastRead;\r\n            }\r\n\r\n            void popFront()\r\n            {\r\n                _lastRead = cast(dchar)_decoder.decodeNext();\r\n            }\r\n\r\n            auto save()\r\n            {\r\n                return this;\r\n            }\r\n        }\r\n\r\n        return ByDchar(this);\r\n    }\r\n\r\n    /// ditto\r\n    auto decode()\r\n    {\r\n        return decode(input);\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    wstring input = \"Жå∑\";\r\n    auto decoder = UTF16LEDecoder(cast(string)input);\r\n    assert(decoder.decodeNext() == 'Ж');\r\n    assert(decoder.decodeNext() == 'å');\r\n    assert(decoder.decodeNext() == '∑');\r\n}\r\n\r\n/**\r\n * UTF-16 LE encoder to use with dlib.text.encodings.transcode\r\n */\r\nstruct UTF16LEEncoder\r\n{\r\n    /**\r\n     * Encodes a Unicode code point to UTF-16 LE into user-provided buffer.\r\n     * Returns number of bytes written, or 0 at error.\r\n     */\r\n    size_t encode(uint ch, char[] buffer)\r\n    {\r\n        wchar[] wbuffer = cast(wchar[])buffer;\r\n        if (ch > 0xFFFF)\r\n        {\r\n            wchar x = cast(wchar)ch;\r\n            wchar vh = cast(wchar)(UTF16_HI_SURROGATE | ((((ch >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10));\r\n            wchar vl = cast(wchar)(UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)));\r\n            wbuffer[0] = vh;\r\n            wbuffer[1] = vl;\r\n            return 4;\r\n        }\r\n        else\r\n        {\r\n            wbuffer[0] = cast(wchar)ch;\r\n            return 2;\r\n        }\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    UTF16LEEncoder enc;\r\n    char[4] buffer;\r\n    size_t numBytes = enc.encode('Ж', buffer);\r\n    assert(numBytes == 2);\r\n    assert(cast(wchar[])(buffer[0..numBytes]) == [0x0416]);\r\n}\r\n\r\n/**\r\n * Converts UTF-8 to UTF-16\r\n * Will be deprecated soon, use transcode!(UTF8Decoder, UTF16LEEncoder) instead\r\n */\r\nwchar[] convertUTF8toUTF16(string s, bool nullTerm = false)\r\n{\r\n    Array!wchar array;\r\n    wchar[] output;\r\n\r\n    UTF8Decoder dec = UTF8Decoder(s);\r\n\r\n    while (!dec.eos)\r\n    {\r\n        int code = dec.decodeNext();\r\n\r\n        if (code == UTF8_ERROR)\r\n        {\r\n            array.free();\r\n            return output;\r\n        }\r\n\r\n        dchar ch = cast(dchar)code;\r\n\r\n        if (ch > 0xFFFF)\r\n        {\r\n            // Split ch up into a surrogate pair as it is over 16 bits long.\r\n            wchar x = cast(wchar)ch;\r\n            auto vh = UTF16_HI_SURROGATE | ((((ch >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10);\r\n            auto vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1));\r\n            array.append(cast(wchar)vh);\r\n            array.append(cast(wchar)vl);\r\n        }\r\n        else\r\n        {\r\n            array.append(cast(wchar)ch);\r\n        }\r\n    }\r\n\r\n    if (nullTerm)\r\n    {\r\n        array.append(0);\r\n    }\r\n\r\n    output = copy(array.data);\r\n    array.free();\r\n    return output;\r\n}\r\n\r\n/**\r\n * Converts UTF-16 zero-terminated string to UTF-8\r\n */\r\nchar[] convertUTF16ztoUTF8(wchar* s, bool nullTerm = false)\r\n{\r\n    Array!char array;\r\n    char[] output;\r\n    wchar* utf16 = s;\r\n\r\n    wchar utf16char;\r\n    do\r\n    {\r\n        utf16char = *utf16;\r\n        utf16++;\r\n\r\n        if (utf16char)\r\n        {\r\n            if (utf16char < 0x80)\r\n            {\r\n                array.append((utf16char >> 0 & 0x7F) | 0x00);\r\n            }\r\n            else if (utf16char < 0x0800)\r\n            {\r\n                array.append((utf16char >> 6 & 0x1F) | 0xC0);\r\n                array.append((utf16char >> 0 & 0x3F) | 0x80);\r\n            }\r\n            else if (utf16char < 0x010000)\r\n            {\r\n                array.append((utf16char >> 12 & 0x0F) | 0xE0);\r\n                array.append((utf16char >> 6 & 0x3F) | 0x80);\r\n                array.append((utf16char >> 0 & 0x3F) | 0x80);\r\n            }\r\n            else if (utf16char < 0x110000)\r\n            {\r\n                array.append((utf16char >> 18 & 0x07) | 0xF0);\r\n                array.append((utf16char >> 12 & 0x3F) | 0x80);\r\n                array.append((utf16char >> 6 & 0x3F) | 0x80);\r\n                array.append((utf16char >> 0 & 0x3F) | 0x80);\r\n            }\r\n        }\r\n    }\r\n    while (utf16char);\r\n\r\n    if (nullTerm)\r\n    {\r\n        array.append(0);\r\n    }\r\n\r\n    output = copy(array.data);\r\n    array.free();\r\n    return output;\r\n}\r\n"
  },
  {
    "path": "dlib/text/utf8.d",
    "content": "/*\r\nCopyright (c) 2015-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * UTF-8 encoder and decoder\r\n *\r\n * Copyright: Timur Gafarov 2015-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov, Roman Chistokhodov\r\n */\r\nmodule dlib.text.utf8;\r\n\r\nimport dlib.text.common;\r\n\r\n/// Constant to return from UTF8Decoder on the end of string.\r\nenum UTF8_END = DECODE_END;\r\n\r\n/// Constant to return from UTF8Decoder when error occurs.\r\nenum UTF8_ERROR = DECODE_ERROR;\r\n\r\n/**\r\n * UTF-8 decoder to use with dlib.text.encodings.transcode\r\n */\r\nstruct UTF8Decoder\r\n{\r\n    public:\r\n    \r\n    /// Input string. Set it before decoding\r\n    string input;\r\n    \r\n    /// Current index in an input string\r\n    size_t index = 0;\r\n    \r\n    /// Current character index\r\n    int character = 0;\r\n\r\n    private:\r\n    int get()\r\n    {\r\n        if (index >= input.length)\r\n            return UTF8_END;\r\n        auto c = input[index] & 0xFF;\r\n        index++;\r\n        return c;\r\n    }\r\n\r\n    int cont()\r\n    {\r\n        int c = get();\r\n        return ((c & 0xC0) == 0x80) ? (c & 0x3F): UTF8_ERROR;\r\n    }\r\n\r\n    public:\r\n    /**\r\n     * Decode next character.\r\n     * Returns: decoded code point, or UTF8_ERROR if error occured, or UTF8_END if input has no more characters.\r\n     */\r\n    int decodeNext()\r\n    {\r\n        int c;  // the first byte of the character\r\n        int r;  // the result\r\n\r\n        if (index >= input.length)\r\n            return index == input.length ? UTF8_END : UTF8_ERROR;\r\n\r\n        character++;\r\n        c = get();\r\n\r\n        // Zero continuation (0 to 127)\r\n        if ((c & 0x80) == 0)\r\n            return c;\r\n\r\n        // One continuation (128 to 2047)\r\n        if ((c & 0xE0) == 0xC0)\r\n        {\r\n            int c1 = cont();\r\n            if (c1 >= 0)\r\n            {\r\n                r = ((c & 0x1F) << 6) | c1;\r\n                return r >= 128 ? r : UTF8_ERROR;\r\n            }\r\n        }\r\n        // Two continuation (2048 to 55295 and 57344 to 65535)\r\n        else if ((c & 0xF0) == 0xE0)\r\n        {\r\n            int c1 = cont();\r\n            int c2 = cont();\r\n            if ((c1 | c2) >= 0)\r\n            {\r\n                r = ((c & 0x0F) << 12) | (c1 << 6) | c2;\r\n                return r >= 2048 && (r < 55296 || r > 57343) ? r : UTF8_ERROR;\r\n            }\r\n        }\r\n        // Three continuation (65536 to 1114111)\r\n        else if ((c & 0xF8) == 0xF0)\r\n        {\r\n            int c1 = cont();\r\n            int c2 = cont();\r\n            int c3 = cont();\r\n            if ((c1 | c2 | c3) >= 0)\r\n            {\r\n                return (((c & 0x0F) << 18) | (c1 << 12) | (c2 << 6) | c3);\r\n            }\r\n        }\r\n\r\n        return UTF8_ERROR;\r\n    }\r\n    \r\n    /**\r\n     * Check if decoder is in the end of input.\r\n     */\r\n    bool eos()\r\n    {\r\n        return (index >= input.length);\r\n    }\r\n\r\n    /**\r\n     * Range interface.\r\n     */\r\n    auto decode(string s)\r\n    {\r\n        input = s;\r\n        \r\n        static struct ByDchar\r\n        {\r\n            private:\r\n            \r\n            UTF8Decoder _decoder;\r\n            dchar _lastRead;\r\n            \r\n            public:\r\n            \r\n            this(UTF8Decoder decoder)\r\n            {\r\n                _decoder = decoder;\r\n                _lastRead = cast(dchar)_decoder.decodeNext();\r\n            }\r\n\r\n            bool empty()\r\n            {\r\n                return _lastRead == UTF8_END || _lastRead == UTF8_ERROR;\r\n            }\r\n\r\n            dchar front()\r\n            {\r\n                return _lastRead;\r\n            }\r\n\r\n            void popFront()\r\n            {\r\n                _lastRead = cast(dchar)_decoder.decodeNext();\r\n            }\r\n\r\n            auto save() {\r\n                return this;\r\n            }\r\n        }\r\n\r\n        return ByDchar(this);\r\n    }\r\n    \r\n    /// ditto\r\n    auto decode()\r\n    {\r\n        return decode(input);\r\n    }\r\n\r\n    ///\r\n    unittest\r\n    {\r\n        auto decoder = UTF8Decoder(\"Eng 日本語 Кир ©€\");\r\n        import std.algorithm: equal;\r\n        assert(equal(decoder.decode(), \"Eng 日本語 Кир ©€\"d));\r\n\r\n        auto range = decoder.decode();\r\n        auto saved = range.save;\r\n\r\n        range.popFront();\r\n        range.popFront();\r\n        range.popFront();\r\n        range.popFront();\r\n        range.popFront();\r\n\r\n        assert(equal(range, \"本語 Кир ©€\"d));\r\n        assert(equal(saved, \"Eng 日本語 Кир ©€\"d));\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    {\r\n        auto decoder = UTF8Decoder(\"Eng 日本語 Кир ©€\\xF0\\x90\\x8D\\x88\");\r\n        assert(decoder.decodeNext() == 'E');\r\n        assert(decoder.decodeNext() == 'n');\r\n        assert(decoder.decodeNext() == 'g');\r\n        assert(decoder.decodeNext() == ' ');\r\n        assert(decoder.decodeNext() == '日');\r\n        assert(decoder.decodeNext() == '本');\r\n        assert(decoder.decodeNext() == '語');\r\n        assert(decoder.decodeNext() == ' ');\r\n        assert(decoder.decodeNext() == 'К');\r\n        assert(decoder.decodeNext() == 'и');\r\n        assert(decoder.decodeNext() == 'р');\r\n        assert(decoder.decodeNext() == ' ');\r\n        assert(decoder.decodeNext() == '©');\r\n        assert(decoder.decodeNext() == '€');\r\n        assert(decoder.decodeNext() == 0x10348);\r\n        assert(decoder.decodeNext() == UTF8_END);\r\n        assert(decoder.get() == UTF8_END);\r\n        assert(decoder.eos());\r\n    }\r\n    {\r\n        auto decoder = UTF8Decoder(\"日本語\"[0..$-1]);\r\n        assert(decoder.decodeNext() == '日');\r\n        assert(decoder.decodeNext() == '本');\r\n        assert(decoder.decodeNext() == UTF8_ERROR);\r\n    }\r\n}\r\n\r\n/**\r\n * UTF-8 encoder to use with dlib.text.encodings.transcode\r\n */\r\nstruct UTF8Encoder\r\n{\r\n    /**\r\n     * Encodes a Unicode code point to UTF-8 into user-provided buffer.\r\n     * Returns number of bytes written, or 0 at error.\r\n     */\r\n    size_t encode(uint c, char[] buffer)\r\n    {\r\n        if (c <= 0x7F)\r\n        {\r\n            // Plain ASCII\r\n            buffer[0] = cast(char)c;\r\n            return 1;\r\n        }\r\n        else if (c <= 0x07FF)\r\n        {\r\n            // 2-byte unicode\r\n            buffer[0] = cast(char)(((c >> 6) & 0x1F) | 0xC0);\r\n            buffer[1] = cast(char)(((c >> 0) & 0x3F) | 0x80);\r\n            return 2;\r\n        }\r\n        else if (c <= 0xFFFF)\r\n        {\r\n            // 3-byte unicode\r\n            buffer[0] = cast(char)(((c >> 12) & 0x0F) | 0xE0);\r\n            buffer[1] = cast(char)(((c >>  6) & 0x3F) | 0x80);\r\n            buffer[2] = cast(char)(((c >>  0) & 0x3F) | 0x80);\r\n            return 3;\r\n        }\r\n        else if (c <= 0x10FFFF)\r\n        {\r\n            // 4-byte unicode\r\n            buffer[0] = cast(char)(((c >> 18) & 0x07) | 0xF0);\r\n            buffer[1] = cast(char)(((c >> 12) & 0x3F) | 0x80);\r\n            buffer[2] = cast(char)(((c >>  6) & 0x3F) | 0x80);\r\n            buffer[3] = cast(char)(((c >>  0) & 0x3F) | 0x80);\r\n            return 4;\r\n        }\r\n        else\r\n        {\r\n            // error\r\n            return 0;\r\n        }\r\n    }\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    UTF8Encoder enc;\r\n    char[4] buffer;\r\n    size_t numBytes = enc.encode('Ж', buffer);\r\n    assert(numBytes == 2);\r\n    assert(buffer[0..numBytes] == [0xD0, 0x96]);\r\n}\r\n"
  },
  {
    "path": "dlib/text/utils.d",
    "content": "/*\r\nCopyright (c) 2016-2025 Timur Gafarov\r\n\r\nBoost Software License - Version 1.0 - August 17th, 2003\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n*/\r\n\r\n/**\r\n * Text processing utils\r\n *\r\n * Copyright: Timur Gafarov 2016-2025.\r\n * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).\r\n * Authors: Timur Gafarov\r\n */\r\nmodule dlib.text.utils;\r\n\r\nimport dlib.core.memory;\r\n\r\n/// Make an copy of a string in unmanaged memory\r\nT[] copy(T)(T[] b)\r\n{\r\n    auto res = New!(T[])(b.length);\r\n    foreach(i, c; b)\r\n        res[i] = c;\r\n    return res;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    auto str = \"hello\".dup;\r\n    auto c = copy(str);\r\n    assert(c == str);\r\n    Delete(c);\r\n}\r\n\r\n/// Make an immutable copy of a string in unmanaged memory\r\nimmutable(T)[] immutableCopy(T)(immutable(T)[] b)\r\n{\r\n    auto res = New!(T[])(b.length);\r\n    foreach(i, c; b)\r\n        res[i] = c;\r\n    return cast(immutable(T)[])res;\r\n}\r\n\r\n/// Concatenates two strings to a new string in unmanaged memory\r\nstring catStr(string s1, string s2)\r\n{\r\n    char[] buffer = New!(char[])(s1.length + s2.length);\r\n    size_t i, j;\r\n    for(i = 0; i < s1.length; i++)\r\n    {\r\n        buffer[i] = s1[i];\r\n    }\r\n    for(j = 0; j < s2.length; j++)\r\n    {\r\n        buffer[i+j] = s2[j];\r\n    }\r\n    return cast(string)buffer;\r\n}\r\n\r\n///\r\nunittest\r\n{\r\n    auto str1 = \"hello\";\r\n    auto str2 = \" world\";\r\n\r\n    auto cat = catStr(str1, str2);\r\n    assert(cat == \"hello world\");\r\n    Delete(cat);\r\n}\r\n"
  },
  {
    "path": "dub.json",
    "content": "{\n    \"name\": \"dlib\",\n    \"description\": \"D language utility library\",\n    \"homepage\": \"http://github.com/gecko0307/dlib\",\n    \"license\": \"BSL-1.0\",\n    \"authors\": [\n        \"Timur Gafarov\",\n        \"Martin Cejp\",\n        \"Andrey Penechko\",\n        \"Vadim Lopatin\",\n        \"Nick Papanastasiou\",\n        \"Oleg Baharev\", \n        \"Roman Chistokhodov\", \n        \"Eugene Wissner\",\n        \"Roman Vlasov\",\n        \"Basile Burg\",\n        \"Valeriy Fedotov\",\n        \"Ferhat Kurtulmuş\"\n    ],\n    \"importPaths\": [\n        \".\"\n    ],\n    \"buildRequirements\":[\n        \"allowWarnings\"\n    ],\n    \"lflags-linux-gdc\": [\"-lz\"],\n    \"configurations\": [\n        {\n            \"name\": \"library\",\n            \"targetType\": \"library\",\n            \"sourcePaths\": [\"dlib\"]\n        },\n        {\n            \"name\": \"import\",\n            \"targetType\": \"sourceLibrary\",\n            \"sourceFiles-posix\": [\"libdlib.a\"],\n            \"sourceFiles-windows\": [\"dlib.lib\"]\n        }\n    ]\n}\n"
  }
]