Repository: gecko0307/dlib Branch: master Commit: e99dda8575d2 Files: 164 Total size: 1.1 MB Directory structure: gitextract_13402xs3/ ├── .editorconfig ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ └── test.yml ├── .gitignore ├── AUTHORS.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── COPYING.md ├── Doxyfile ├── README.md ├── dlib/ │ ├── audio/ │ │ ├── io/ │ │ │ ├── package.d │ │ │ └── wav.d │ │ ├── package.d │ │ ├── sample.d │ │ ├── sound.d │ │ ├── synth.d │ │ └── unmanaged.d │ ├── coding/ │ │ ├── package.d │ │ ├── varint.d │ │ └── zlib.d │ ├── concurrency/ │ │ ├── package.d │ │ ├── taskqueue.d │ │ ├── threadpool.d │ │ └── workerthread.d │ ├── container/ │ │ ├── array.d │ │ ├── bst.d │ │ ├── buffer.d │ │ ├── dict.d │ │ ├── linkedlist.d │ │ ├── mappedlist.d │ │ ├── package.d │ │ ├── queue.d │ │ ├── spscqueue.d │ │ └── stack.d │ ├── core/ │ │ ├── bitio.d │ │ ├── compound.d │ │ ├── memory.d │ │ ├── mutex.d │ │ ├── oop.d │ │ ├── ownership.d │ │ ├── package.d │ │ ├── stream.d │ │ ├── thread.d │ │ └── tuple.d │ ├── filesystem/ │ │ ├── delegaterange.d │ │ ├── dirrange.d │ │ ├── filesystem.d │ │ ├── local.d │ │ ├── package.d │ │ ├── posix/ │ │ │ ├── common.d │ │ │ ├── directory.d │ │ │ └── file.d │ │ ├── stdfs.d │ │ ├── stdposixdir.d │ │ ├── stdwindowsdir.d │ │ └── windows/ │ │ ├── common.d │ │ ├── directory.d │ │ └── file.d │ ├── geometry/ │ │ ├── aabb.d │ │ ├── frustum.d │ │ ├── intersection.d │ │ ├── mpr.d │ │ ├── obb.d │ │ ├── package.d │ │ ├── plane.d │ │ ├── ray.d │ │ ├── sphere.d │ │ ├── support.d │ │ ├── triangle.d │ │ ├── trimesh.d │ │ └── utils.d │ ├── image/ │ │ ├── animation.d │ │ ├── arithmetics.d │ │ ├── canvas.d │ │ ├── color.d │ │ ├── filters/ │ │ │ ├── binarization.d │ │ │ ├── boxblur.d │ │ │ ├── chromakey.d │ │ │ ├── contrast.d │ │ │ ├── convolution.d │ │ │ ├── desaturate.d │ │ │ ├── edgedetect.d │ │ │ ├── histogram.d │ │ │ ├── lens.d │ │ │ ├── median.d │ │ │ ├── morphology.d │ │ │ ├── normalmap.d │ │ │ ├── package.d │ │ │ └── sharpen.d │ │ ├── fthread.d │ │ ├── hdri.d │ │ ├── hsv.d │ │ ├── image.d │ │ ├── io/ │ │ │ ├── bmp.d │ │ │ ├── hdr.d │ │ │ ├── jpeg.d │ │ │ ├── package.d │ │ │ ├── png.d │ │ │ ├── tga.d │ │ │ └── utils.d │ │ ├── package.d │ │ ├── render/ │ │ │ ├── cosplasma.d │ │ │ ├── package.d │ │ │ ├── shapes.d │ │ │ └── text.d │ │ ├── resampling/ │ │ │ ├── bicubic.d │ │ │ ├── bilinear.d │ │ │ ├── lanczos.d │ │ │ ├── nearest.d │ │ │ └── package.d │ │ ├── signal2d.d │ │ ├── transform.d │ │ └── unmanaged.d │ ├── math/ │ │ ├── combinatorics.d │ │ ├── complex.d │ │ ├── decomposition.d │ │ ├── diff.d │ │ ├── dual.d │ │ ├── dualquaternion.d │ │ ├── fft.d │ │ ├── hof.d │ │ ├── interpolation/ │ │ │ ├── bezier.d │ │ │ ├── catmullrom.d │ │ │ ├── easing.d │ │ │ ├── hermite.d │ │ │ ├── linear.d │ │ │ ├── nearest.d │ │ │ ├── package.d │ │ │ └── smoothstep.d │ │ ├── linsolve.d │ │ ├── matrix.d │ │ ├── package.d │ │ ├── quaternion.d │ │ ├── sse.d │ │ ├── tensor.d │ │ ├── transformation.d │ │ ├── utils.d │ │ └── vector.d │ ├── memory/ │ │ ├── allocator.d │ │ ├── arena.d │ │ ├── gcallocator.d │ │ ├── mallocator.d │ │ ├── mmappool.d │ │ └── package.d │ ├── network/ │ │ ├── errno.d │ │ ├── package.d │ │ ├── socket.d │ │ └── url.d │ ├── package.d │ ├── random/ │ │ ├── package.d │ │ └── random.d │ ├── serialization/ │ │ ├── json.d │ │ ├── package.d │ │ └── xml.d │ └── text/ │ ├── common.d │ ├── encodings.d │ ├── lexer.d │ ├── package.d │ ├── str.d │ ├── utf16.d │ ├── utf8.d │ └── utils.d └── dub.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*.{c,h,d,di,dd,sh}] end_of_line = crlf insert_final_newline = true indent_style = space indent_size = 4 trim_trailing_whitespace = true charset = utf-8 dfmt_brace_style = allman dfmt_soft_max_line_length = 120 dfmt_align_switch_statements = true dfmt_outdent_attributes = true dfmt_outdent_labels = true dfmt_split_operator_at_line_end = true dfmt_space_after_cast = false dfmt_space_after_keywords = true dfmt_space_before_function_parameters = false dfmt_selective_import_space = false dfmt_keep_line_breaks = true ================================================ FILE: .github/FUNDING.yml ================================================ patreon: gecko0307 liberapay: gecko0307 ================================================ FILE: .github/workflows/test.yml ================================================ name: CI on: push: branches: [ master ] paths: - 'dlib/**' - 'dub.json' - '.github/workflows/**' pull_request: branches: [ master ] paths: - 'dlib/**' - 'dub.json' - '.github/workflows/**' jobs: test: name: Dub Tests strategy: matrix: os: [ubuntu-latest, windows-latest] dc: [dmd-latest, ldc-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - uses: dlang-community/setup-dlang@v1 with: compiler: ${{ matrix.dc }} - name: Run tests run: dub test --build=unittest-cov - name: Run code coverage if: success() run: | curl https://codecov.io/bash > codecov.sh bash codecov.sh ================================================ FILE: .gitignore ================================================ # Compiled Object files *.o *.obj # Compiled Static libraries *.a *.lib # Executables *.exe # Dub files .dub dub.selections.json # Dub test files __test__library__ tests/ # Documentation docs/ docs-doxygen/ docs.json __dummy.html documentation.chm # Coverage files *.lst # Debug info *.pdb # Backup files *~ ================================================ FILE: AUTHORS.md ================================================ # dlib authors and contributors * Core library - [Timur Gafarov aka gecko0307](https://github.com/gecko0307) * Filesystem package, stream module - [Martin Cejp aka minexew](https://github.com/minexew) * Memory package, socket module - [Eugene Wissner aka belka-ew](https://github.com/belka-ew) * Image binarization, histogram generation, median filter - [LightHouse Software](http://lhs-blog.info/) / [Oleg Baharev aka aquaratixc](https://github.com/aquaratixc) * TGA and BMP encoder, improved BMP decoder, bugfixes, unittests - [Roman Chistokhodov aka FreeSlave](https://github.com/FreeSlave) * PNG decoder improvements - [Vadim Lopatin](https://github.com/buggins) * Combinatorics module - Nick Papanastasiou * Rectangle drawing - [Aaron Nédélec aka ReactiveAlkali](https://github.com/ReactiveAlkali) * Vector swizzling assignment - [João Lourenço aka iK4tsu](https://github.com/iK4tsu) * SSE vector math port for GDC - [Alexander Perfilyev](https://github.com/aperfilev) * 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) * Additional unittests - [Roman Vlasov](https://github.com/VlasovRoman) ================================================ FILE: CHANGELOG.md ================================================ dlib 1.5.0 - TBD ---------------- - **dlib.math** - `trsMatrix` that creates a transformation matrix from translation/rotation/scaling a once, without three matrix multiplications - New easing function `easeOutElastic`. dlib 1.4.1 - 1 Jan, 2026 ------------------------ - **dlib.geometry** - Fix setting `OBB.center`. dlib 1.4.0 - 1 Dec, 2025 ------------------------ Changes since dlib 1.4.0 beta1: - **dlib.math** - `Matrix.isAffine`, `Matrix.inverse`, `Matrix.getColumn`, `Quaternion.toMatrix4x4`, `Quaternion.toMatrix3x3` are now `const`. dlib 1.4.0 beta1 - 31 Nov, 2025 ------------------------------- - **dlib.memory** - `dlib.memory.arena` - a general-purpose region-based memory allocator - **dlib.container** - `dlib.container.spscqueue` - wait-free single-producer single-consumer queue - `dlib.container.mappedlist` - a list with string-mapped indices - **dlib.math** - New function `wrapAngle` in `dlib.math.utils`. dlib 1.3.3 - 30 Aug, 2025 ------------------------- - **dlib.serialization** - Fix bugs in `dlib.serialization.json`. dlib 1.3.2 - 1 May, 2025 ------------------------ - **dlib.math** - `lookAtMatrix` now checks for matching eye and up directions - Fix unittest for vector swizzling. dlib 1.3.1 - 10 Mar, 2025 ------------------------- - **dlib.math** - Limit `dlib.math.sse` for x86_64 only - `dlib.math.complex.zeta` was removed due to its broken status. dlib 1.3.0 - 21 Feb, 2023 ------------------------- No changes since dlib 1.3.0 beta1. dlib 1.3.0 beta1 - 14 Feb, 2023 ------------------------------- - **dlib.random** - New module `dlib.random.random` that implements `random`, a pseudo-random number generator based on C `rand` - **dlib.math** - Function that computes quadratic Bézier curve - `dlib.math.interpolation.bezierQuadratic` - GNU D Compiler (GDC) support in `dlib.math.sse` dlib 1.2.1 - 30 Aug, 2023 ------------------------- - **dlib.image** - Median filter (`dlib.image.filters.median`) - **dlib.filesystem** - Bugfixes in `dlib.filesystem.posix.common` - **dlib.core** - `dlib.core.thread`: fix deprecations in Windows multithreading API signatures - **dlib.math** - Fix unittest for `dlib.math.utils.nextPowerOfTen` - **Misc** - Added `AUTHORS.md`. dlib 1.2.0 - 30 Apr, 2023 ------------------------- No changes since dlib 1.2.0 beta1. dlib 1.2.0 beta1 - 19 Apr, 2023 ------------------------------- - **dlib.math** - `homothetyMatrix` and `homothetyMatrix2D` functions in `dlib.math.transformation` - `radtorev` and `revtorad` functions in `dlib.math.utils` that convert radians to revolutions and revolutions to radians, respectively - **dlib.image** - New funtion `drawRect` in `dlib.image.render.shapes` - **Misc** - Added `CODE_OF_CONDUCT.md` - Doxygen support for documentation. dlib 1.1.0 - 5 Oct, 2022 ------------------------- No changes since dlib 1.1.0 beta1. dlib 1.1.0 beta1 - 10 Sep, 2022 ------------------------------- - **dlib.geometry** - New module `dlib.geometry.mpr` - implementation of the Minkowski Portal Refinement algorithm that detects intersection between two arbitrary convex shapes - New module `dlib.geometry.support` with support functions for some common shapes - **dlib.math** - `integer` and `frac` functions in `dlib.math.utils` that return integer part and fractional part of a real number, respectively. - **dlib.image** - Fix compilation for x86. dlib 1.0.0 - 17 Feb, 2022 ------------------------- No changes since dlib 1.0.0 beta2. dlib 1.0.0 beta2 - 05 Feb, 2022 ------------------------------- Changes since dlib 1.0.0 beta1: - **dlib.image** - File-based image loading functions now preload data to memory, so that decoders run faster (2x-10x depending on format and image size). dlib 1.0.0 beta1 - 12 Jan, 2022 ------------------------------- - **dlib.core** - **Breaking change:** `dlib.core.bitio.swapEndian16` moved to `dlib.math.utils` - POSIX thread creation is now validated in debug mode - **dlib.math** - **Breaking change:** deprecated method `Quaternion.generator` has been removed - **Breaking change:** deprecated functions `sum`, `invertArray`, `allIsZero` in `dlib.math.utils` have been removed - `interpHermiteDerivative` - `interpHermite` now support vector types - `Complexd` alias to `Complex!(double)` in `dlib.math.complex` - Fix `dlib.math.complex.pow`, `dlib.math.complex.atan2` - **dlib.geometry** - **Breaking change:** deprecated method `Ray.intersectSphere` with `position` and `radius` arguments has been removed - **Breaking change:** deprecated method `Ray.intersectTriangle` with `v0`, `v1`, `v2` arguments has been removed. - Fix `Triangle.boundingBox` - New function `intrSphereVsAABB` in `dlib.geometry.intersection` - `AABB.intersectsSphere` is deprecated, use `intrSphereVsAABB` instead. dlib 0.23.0 - 1 Oct, 2021 ------------------------- No changes since dlib 0.23.0 beta1. dlib 0.23.0 beta1 - 28 Sep, 2021 -------------------------------- - **dlib.math** - Vector swizzling assign support: `v.zxy = Vector3f(1, 2, 3);` - `Quaternion.generator` is deprecated, use `Quaternion.rotationAxis` and `Quaternion.rotationAngle` instead - `Quaternion.fromEulerAngles` and `Quaternion.toEulerAngles` now use angles in pitch-yaw-roll format - `EPSILON` in `dlib.math.utils` now equals 0.000001. - **dlib.geometry** - `Ray.intersectSphere` with `position` and `radius` arguments is deprecated, use `Ray.intersectSphere` with `Sphere` struct instead - `Ray.intersectTriangle` with `v0`, `v1`, `v2` arguments is is deprecated, use `Ray.intersectTriangle` with `Triangle` struct instead - **dlib.image** - `isAlmostZero` for `Color4f` - **dlib.filesystem** - `StdFileSystem.openDir` now returns null if path is not a valid directory. dlib 0.22.0 - 13 Jun, 2021 -------------------------- No changes since dlib 0.22.0 beta1. dlib 0.22.0 beta1 - 26 May, 2021 -------------------------------- - **dlib.core** - **Breaking change:** removed `dlib.core.oop.implements` (non-working function) - **dlib.math** - Fix matrix subtraction - Fix wrong bounds check in matrix slice assignment - **Breaking change:** removed `dlib.math.linsolve.solveGS` (non-working function) - Binary matrix operations are now `const` - `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 - **dlib.image** - **Breaking change:** deprecated type `dlib.image.image.PixelFormat` has been removed - **Breaking change:** deprecated aliases `save` and `load` in `dlib.audio.io` have been removed - **dlib.audio** - **Breaking change:** deprecated aliases `save`, `load`, `saveAnimated`, `loadAnimated`, `saveHDRI`, `loadHDRI` in `dlib.image.io` have been removed - **dlib.text** - **Breaking change:** deprecated method `UTF8Decoder.byDChar` have been removed - **Breaking change:** deprecated method `UTF16LEDecoder.byDChar` have been removed - **Breaking change:** deprecated aliases `UTF16Decoder` and `UTF16Encoder` have been removed - **dlib.serialization** - Boolean values support in JSON decoder. dlib 0.21.0 - 7 Apr, 2021 ------------------------- No changes since dlib 0.21.0 beta2. dlib 0.21.0 beta2 - 28 Mar, 2021 -------------------------------- Changes since dlib 0.21.0 beta1: - **dlib.image** - `PixelFormat` is deprecated, use `IntegerPixelFormat` instead. dlib 0.21.0 beta1 - 22 Feb, 2021 -------------------------------- - **dlib.text** - **Breaking change:** deprecated module `dlib.text.unmanagedstring` has been removed - **Breaking change:** deprecated method `String.byDChar` has been removed - `UTF16Decoder` and `UTF16Encoder` are deprecated, use `UTF16LEDecoder` and `UTF16LEEncoder` instead - **dlib.image** - **Breaking change:** deprecated module `dlib.image.io.io` has been removed - `load` and `save` are deprecated, use `loadImage` and `saveImage` instead - `loadAnimated` and `saveAnimated` are deprecated, use `loadAnimatedImage` and `saveAnimatedImage` instead - `loadHDRI` and `saveHDRI` are deprecated, use `loadHDRImage` and `saveHDRImage` instead - Fix integer overflow in `Image.getPixel` and `Image.setPixel` - **dlib.container** - **Breaking change:** deprecated alias `DynamicArray` has been removed - **dlib.coding** - **Breaking change:** deprecated module `dlib.coding.hash` has been removed - **dlib.audio** - `load` and `save` are deprecated, use `loadSound` and `saveSound` instead - **dlib.serialization** - Fix a bug in JSON decoder - **Misc** - Switched from Travis CI to GitHub Actions for running integration tests. dlib 0.20.0 - 16 Oct, 2020 -------------------------- No changes since dlib 0.20.0 beta1. dlib 0.20.0 beta1 - 10 Oct, 2020 -------------------------------- - **dlib.image** - `dlib.image.io.io` is deprecated, import `dlib.image.io` instead - **dlib.audio** - New package: `dlib.audio.io` - **dlib.text** - `dlib.text.unmanagedstring` is deprecated, use `dlib.text.str` instead - `String.byDChar` is deprecated, use `String.decode` instead - `UTF8Decoder.byDChar` is deprecated, use `UTF8Decoder.decode` instead - `UTF16Decoder.byDChar` is deprecated, use `UTF16Decoder.decode` instead - **dlib.coding** - `dlib.coding.hash` is deprecated, use `std.digest` instead - **dlib.container** - `dlib.container.array.DynamicArray` is deprecated, use `dlib.container.array.Array` instead - **Documentation** - 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. - Many modules are now documented better. - **Misc** - Added latest DMD (2.094.0, 2.093.1) and LDC (1.23.0) to Travis CI config. dlib 0.19.2 - 26 Aug, 2020 -------------------------- - A couple of fixes for LDC - New AppVeyor configuration. dlib 0.19.1 - 24 July, 2020 --------------------------- - **dlib.network** - Fixed compilation under Windows - **dlib.filesystem** - `isFile`, `isDir` properties now work for `StdFileSystem` entries - **dlib.container** - `DynamicArray.readOnlyData` - **dlib.text** - `String.toString` and `String.ptr` are now `const` - **Misc** - Added latest DMD (2.093.0) and LDC (1.22.0) to Travis CI config. dlib 0.19.0 - 31 May, 2020 -------------------------- Changes since beta2: - **dlib.coding**, **dlib.filesystem** - Deprecation fixes - **Misc** - Added latest DMD (2.092.0) to Travis CI config. dlib 0.19.0 beta2 - 22 May, 2020 -------------------------------- Changes since beta1: - **dlib.math** - Transformation of a vector with 4x4 matrix now doesn't include affinity check. - `dlib.math.transformation.scaling` fix. dlib 0.19.0 beta1 - 8 May, 2020 ------------------------------- - **dlib.core** - New module `dlib.core.mutex`, a thin abstraction over platform-specific thread synchronization primitives. - `Thread.sleep` - **dlib.concurrency** - New package that implements a simple thread pool. - **dlib.image** - New module `dlib.image.render.text` that provides `drawText`, a function to render ASCII strings on images. - **dlib.math** - **Breaking change:** deprecated modules `dlib.math.easing`, `dlib.math.smoothstep` have been removed. - **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)`. - **dlib.geometry** - **Breaking change:** deprecated modules `dlib.geometry.bezier`, `dlib.geometry.hermite` have been removed. - **Breaking change:** deprecated package `dlib.functional` has been removed. - **dlib.text** - `UTF16Encoder` in `dlib.text.utf16`. - `String` can now be constructed directly from `InputStream`. - **Breaking change:** deprecated property `String.cString` has been removed. - **dlib.container** - `Queue` and `Stack` now use `DynamicArray` internally instead of `LinkedList`. - **Misc** - Added latest DMD (2.091.1, 2.090.1) and LDC (1.21.0, 1.20.0) to Travis CI config. dlib 0.18.0 - 28 Feb, 2020 -------------------------- No changes since dlib 0.18.0 beta1. dlib 0.18.0 beta1 - 23 Feb, 2020 -------------------------------- - **dlib.math** - 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. - **dlib.audio** - `SawtoothWaveSynth`, `TriangleWaveSynth` in `dlib.audio.synth`. - **dlib.text** - `String` is now always null-terminated. - **dlib.functional** - The whole package is now deprecated. - `dlib.functional.hof` module moved to `dlib.math.hof`. - `dlib.functional.range` module is deprecated, use `std.range.iota` instead. - **Misc** - Added latest DMD (2.090.1, 2.089.1) and LDC (1.19.0, 1.18.0) to Travis CI config. dlib 0.17.0 - 21 Oct, 2019 -------------------------- Changes since beta: - **dlib.container** - `dlib.container.array`: iterating over array via `foreach_reverse`. dlib 0.17.0 beta1 - 5 Oct, 2019 ------------------------------- - **dlib.core** - `BufferedStreamReader` in `dlib.core.stream` - a simple input range to read fixed chunks of data from an `InputStream`. - **dlib.image** - **Breaking change:** `dlib.image.compleximage` has been removed. - `dlib.image.signal2d` is now fully GC-free. - Filtering of indexed images in PNG decoder is now supported (#142). - **dlib.math** - `dlib.math.tensor` now uses `dlib.core.memory` for internal allocations. - **Breaking change:** deprecated function `identityQuaternion` has been removed. Use `Quaternion.identity` instead. - **dlib.geometry** - **Breaking change:** deprecated aliases `bezierCurveFunc2D` and `bezierCurveFunc3D` have been removed. Use `bezierVector2` and `bezierVector3` instead. - **Misc** - Added latest DMD (2.088.0, 2.087.1) and LDC (1.17.0, 1.16.0) to Travis CI config. dlib 0.16.0 - 30 Mar, 2019 -------------------------- No changes since dlib 0.16.0 beta1. dlib 0.16.0 beta1 - 4 Mar, 2019 ------------------------------- - **dlib.core** - `dlib.core.memory`: Memory profiler now reports file and line of each allocation. Now it is enabled in runtime using `enableMemoryProfiler` function. - `dlib.core.memory`: `Owner.deleteOwnedObject`. - **dlib.text** - `dlib.text.lexer`: `Lexer.position`. - `dlib.text.unmanagedstring`: `String.cString`. - **Breaking change:** deprecated module `dlib.text.slicelexer` has been removed. - **dlib.container** - `dlib.container.array`: `DynamicArray.removeFirst`. - **Breaking change:** deprecated module `dlib.container.hash` has been removed. - **dlib.image** - **Breaking change:** deprecated module `dlib.image.parallel` has been removed. - **dlib.math** - New module `dlib.math.easing` - some basic easing functions for fancy interpolation. - **Breaking change:** deprecated module `dlib.math.fixed` has been removed. - `dlib.math.quaternion`: fixed a bug in `Quaternion.rotationAxis`. - **dlib.geometry** - `dlib.geometry.trimesh` not doesn't use GC. - **dlib.serialization** - `dlib.serialization.json` - GC-free JSON parser. - **dlib.functional** - **Breaking change:** deprecated module `dlib.functional.combinators`, `dlib.functional.quantifiers` has been removed. - **Breaking change:** deprecated `map`, `filter`, `reduce` functions from `dlib.functional.range` have been removed. - **Misc** - Added latest DMD (2.085.0, 2.084.1) and LDC (1.13.0, 1.14.0) to Travis CI config. dlib 0.15.0 - 9 Nov, 2018 ------------------------- - **dlib.container** - `opSlice` and `$` for `dlib.container.array.DynamicArray`. dlib 0.15.0 beta1 - 4 Nov, 2018 ------------------------------- - **dlib.core** - **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. - **dlib.text** - New module `dlib.text.unmanagedstring` that provides `String`, a GC-free UTF8 string type based on `DynamicArray`. - `UTF16Decoder` in `dlib.text.utf16`, `UTF8Encoder` in `dlib.text.utf8`. - `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[])`. - `dlib.text.common` that defines `DECODE_END` and `DECODE_ERROR` for decoders to use. - **dlib.container** - `reserve` and `resize` for `dlib.container.array.DynamicArray` (#151). - **Breaking change**: deprecated `dlib.container.aarray` module has been removed. Use `dlib.container.dict` instead. - **dlib.image** - Fixed unintentional fallthrough in `dlib.image.io.saveImage` that caused error on TGA image. - More accurate path filling in `dlib.image.canvas`. - `dlib.image.parallel` has been deprecated. - **dlib.math** - **Breaking change**: deprecated `dlib.math.affine` module has been removed. Use `dlib.math.transformation` instead. - `dlib.math.fixed` has been deprecated. - **dlib.functional** - `dlib.functional.quantifiers` has been deprecated. - Free functions in `dlib.functional.range` (`map`, `filter`, `reduce`) have been deprecated. Use corresponding Phobos functions instead. - **Misc** - dlib now can be built with recent GNU D Compiler (GDC). **Note:** `dlib.math.sse` is not supported with GDC. - Added latest DMD (2.083.0, 2.082.1) and LDC (1.12.0) to Travis CI config. - Support for [harbored-mod](https://github.com/dlang-community/harbored-mod) documentation generator. dlib 0.14.0 - 9 Jul, 2018 ------------------------- No changes since dlib 0.14.0 beta1. dlib 0.14.0 beta1 - 8 Jul, 2018 ------------------------------- **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). - **dlib.image** - **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. - `PixelFormat.RGBA_FLOAT` is now deprecated, use `FloatPixelFormat.RGBAF32` from `dlib.image.hdri` instead. - Saving to HDR is now supported (`saveHDR` functions in `dlib.image.io.hdr`). - New filters: `dlib.image.filters.histogram` (generates an image histogram) and `dlib.image.filters.binarization` (image thresholding using Otsu's method). - ACES tonemapper (`hdrTonemapACES`) and average luminance function (`averageLuminance`) in `dlib.image.hdri`. - Improved `dlib.image.canvas`. Path rasterizer now natively does anti-aliasing. Fixed bug with rendering on non-square images. - **dlib.audio** - 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`. - **dlib.math** - New module `dlib.math.smoothstep` with sigmoid-like functions: `hermiteSmoothstep`, `rationalSmoothstep`. - **dlib.core** - DMD 2.081.0 compatibility fix in `dlib.core.stream`. - **Misc** - 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. dlib 0.13.0 - 14 May, 2018 -------------------------- No changes since dlib 0.13.0 beta1. dlib 0.13.0 beta1 - 9 May, 2018 ------------------------------- - **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). - **dlib.image** - 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`. - Improved HDR file decoder. Now it supports HDR files with magic string `#?RGBE`. - Reinhard and Hable tonemappers in `dlib.image.hdri`: `hdrTonemapReinhard` and `hdrTonemapHable`. - New filters in `dlib.image.filters.edgedetect`: `edgeDetectLaplace` and `edgeDetectSobel`. - New methods for `Color4f`: `toLinear` and `toGamma`. - Fixed bugs in `dlib.image.arithmetics` module. - **dlib.math** - New functions in `dlib.math.vector`: `reflect`, `refract`, `faceforward`. - New functions in `dlib.math.utils`: `min2` and `max2`. - **dlib.geometry** - New functions in `dlib.geometry.bezier`: `bezierTangentVector2` and `bezierTangentVector3`. - **Misc** - Added latest DMD (2.080.0, 2.079.1) and LDC (1.9.0, 1.8.0) to Travis CI config. - dlib now does CI under Windows using [AppVeyor](https://www.appveyor.com/). dlib 0.12.2 - 7 Nov, 2017 ------------------------- * Enum constants of type `Vector` now can be assigned to variables. * 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. dlib 0.12.1 - 28 Oct, 2017 -------------------------- * Fixed loading 16-bit PNG images * Corrected Bézier function. dlib 0.12.0 - 16 Oct, 2017 -------------------------- No changes since dlib 0.12.0 beta1. dlib 0.12.0 beta1 - 9 Oct, 2017 ------------------------------- - **dlib.core** - 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. - New module `dlib.core.fiber` - initial fibers implementation (Linux-only for now). - **dlib.container** - Containers now use Phobos-conforming method names. Old names are still supported via aliases. - `DynamicArray` now supports inserting and removing values by arbitrary indices (`insertKey` and `removeKey`). - `~=` operator support for `LinkedList`. - Full unittest coverage of `dlib.container.array`. - More unittests for `dlib.container.dict`. - **dlib.image** - New class `UnmanagedAnimatedImage` - GC-free counterpart of `AnimatedImage`. - **Breaking change:** `dlib.image.tone.contrast` is now `dlib.image.filters.contrast`. - `dlib.image.fthread` is now based on `dlib.core.thread`. - **dlib.filesystem** - File access rights in `FileStat`. - Nanosecond modification time precision support in `stat` under Posix. - **dlib.math** - New direct solver (`solve`) in `dlib.math.linsolve` based on LUP decomposition. - **dlib.geometry** - Frustum-sphere intersection test (`intersectsSphere`) for `dlib.geometry.frustum`. - **dlib.coding** - **Breaking change:** `dlib.coding.huffman` is merged with `dlib.image.io.jpeg`. - **Misc** - Added latest DMD (2.075.1, 2.076.0) and LDC (1.3.0, 1.4.0) to Travis CI config. dlib 0.11.1 - 24 May, 2017 -------------------------- * Added `alphaOver` in `dlib.image.color` * Fixed memory leak in `dlib.image.io.png` * Deprecation fix: use `dlib.math.transformation` everywhere instead of `dlib.math.affine`. dlib 0.11.0 - 3 May, 2017 ------------------------- Changes from beta: * Merged `idct.d` with `jpeg.d`, use `dlib.math.transformation` in `dlib.image.transform` * Added `hdrTonemapAverageLuminance` to `dlib.image.hdri` * Fixed memory leak in HDR decoder dlib 0.11.0 beta1 - 25 Apr, 2017 -------------------------------- - **dlib.core** - `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. - **dlib.memory** - Added `GCallocator`, an allocator based on on D's built-in garbage collector. - **dlib.image** - Full-featured APNG support in `dlib.image.io.png` with dispose and blend operations. Saving animations to APNG is also supported. - **dlib.filesystem** - Added `traverseDir`, GC-free recursive directory scanner. - **dlib.math** - `distance` and `distancesqr` overloads for 2D vectors. - `dlib.math.affine` is now deprecated. `dlib.math.transformation` should be used instead. - **dlib.async** - Fixed segfault in event loop. - **Misc** - Removed deprecated `dlib.xml` package. `dlib.serialization.xml` should be used instead. - Added latest DMD (2.074.0) and LDC (1.2.0) to Travis CI config. - A new logo and homepage for the project: https://gecko0307.github.io/dlib. dlib 0.10.1 - 14 Mar, 2017 -------------------------- * Animated images and basic APNG support (unfinished, without dispose and blend operations, saving to APNG is also missing) * Fixed some bugs in `dlib.text.slicelexer` and `dlib.serialization.xml`. `dlib.text.lexer.Lexer` is now an alias to `dlib.text.slicelexer.SliceLexer` * Added latest DMD (2.073.2) and LDC (1.1.0) to Travis CI config. dlib 0.10.0 - 23 Jan, 2017 -------------------------- Changes from beta: - 64-bit fix in `dlib.network.socket` under Windows - Unittest fix in `dlib.filesystem.local` - Code cleanup, use consistent line endings and indentations everywhere - EditorConfig support - dlib now compiles with DMD 2.073.0 and LDC 1.1.0-beta6. dlib 0.10.0 beta1 - 13 Jan, 2017 -------------------------------- - **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 - **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). - **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 - XML parser (`dlib.serialization.xml`) is now fully GC-free - **dlib.network** - `dlib.network.socket`, a cross-platform socket API. Supports Windows and Posix - **dlib.image** - 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 - Radiance HDR/RGBE format support (only loading for now) - **dlib.container** - New module - `dlib.container.buffer`, an interface for input/output buffers - Fixed some issues in `dlib.container.array` - **dlib.text** - Improved `SliceLexer` (fixed bug with multicharacter delimiters) - Added `dlib.text.utils.immutableCopy` - **dlib.math** - `dlib.math.vector.normal` is now `dlib.math.vector.planeNormal` - **Other improvements** - Added latest DMD (2.072.2) to Travis CI config. Many thanks to [Eugene Wissner](https://github.com/belka-ew) for implementing `dlib.async`, `dlib.memory` and `dlib.network`. dlib 0.9.2 - 11 Jun, 2016 ------------------------- - Fixed building with DMD 2.071.1 dlib 0.9.1 - 9 Jun, 2016 ------------------------ - Added `SliceLexer` in `dlib.text` - Fixed wrong `opApply` in `DynamicArray` and `Trie` dlib 0.9.0 - 23 May, 2016 ------------------------- Changes from beta: - Bugfix and unittests for `ArrayStream` - Fixed loading of 32-bit BMP with bitfield masks. dlib 0.9.0 beta1 - 14 May, 2016 ------------------------------- - dlib.network - A new package for networking. So far it contains only one module, `dlib.network.url` - an URL parser - dlib.image - 2-dimensional iteration for images. Also there are now `ImageRegion` and `ImageWindowRange` that simplify writing kernel filters - `dlib.image.transform` module implements affine transformations for images: translation, rotation and scaling. Transformation with arbitrary 3x3 matrix is also possible - Improved BMP and TGA support: new color modes and RLE8 for BMP, saving BMP and TGA - Improved `boxBlur` - `getPixel` and `setPixe` in `Image` class are now public - dlib.math - New `dlib.math.tensor` module implements generic multidimensional array, both with static and dynamic memory allocation - dlib.container - Improved `LinkedList`, added range interface. Added unittests for `LinkedList` and `DynamicArray` - dlib.text - `UTF8Decoder` and `Lexer` now support range interface. Added unittests for both - Other improvements - Added latest DMD (2.071.0) to Travis CI config, added DUB service files to .gitignore. dlib 0.8.1 - 13 Feb, 2016 ------------------------- Minor bugfix release: `saveWav` in `dlib.audio.io.wav` now uses `Sound` interface instead of `GenericSound` class. dlib 0.8.0 - 12 Feb, 2016 ------------------------- Changes from beta: * Fixed #87 dlib 0.8.0 beta1 - 7 Feb, 2016 ------------------------------ * dlib.audio * `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). * `dlib.audio.synth` implements some basic sound synthesizers (sine wave and white noise) * `dlib.audio.io.wav` - uncompressed RIFF/WAV encoder and decoder * dlib.image * All image filters, arithmetic operations, etc. now support manual memory management * New chroma key filter based on Euclidean distance (`dlib.image.filters.chromakey.chromaKeyEuclidean`) * New edge detection filter based on morphological gradient (`dlib.image.filters.edgedetect.edgeDetectGradient`) * Several important bugfixes (image convolution, lanczos and bicubic resampling, wrong deallocation of empty JPEGImage) * dlib.core * Fixed erroneous deleting uninitialized thread in `dlib.core.thread` * dlib.filesystem * 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 * Other improvements * Added [HTML documentation generator](https://github.com/gecko0307/dlib/tree/master/gendoc). dlib 0.7.1 - 2 Dec, 2015 ------------------------ Mostly bugfix release. * Fixed wrong iteration of `dlib.container.dict.Trie` * `_allocatedMemory` in `dlib.core.memory` is now marked as `__gshared`, thus working correctly with `dlib.core.thread` * Fixed wrong behaviour of `nextPowerOfTwo` in `dlib.math.utils` * Ambiguous `rotation` functions in `dlib.math.affine` and `dlib.math.quaternion` are renamed into `rotation2D` and `rotationQuaternion`, respectively * Added `flatten` method for matrices. dlib 0.7.0 - 2 Oct, 2015 ------------------------ Changes from beta: * Fixed 64-bit issues * dlib now compiles with latest LDC * Continuous integration using Travis-CI: https://travis-ci.org/gecko0307/dlib dlib 0.7.0 beta1 - 28 Sep, 2015 ------------------------------- * dlib.core * Added GC-free, Phobos-independent thread module - `dlib.core.thread` * dlib.text * A new package for GC-free text processing. Includes UTF-8 decoder (`dlib.text.utf8`) and general-purpose lexical analyzer (`dlib.text.lexer`) * dlib.xml * XML parser is now GC-free and based on `dlib.text.lexer` * dlib.container * Added GC-free LinkedList (`dlib.container.linkedlist`), Stack (`dlib.container.stack`), Queue (`dlib.container.queue`) * dlib.image * Fixed segfault with non-transparent indexed PNG loading * dlib.math * Fixed error with instancing of vectors with size larger than 4 * Other improvements * Added Travis-CI support dlib 0.6.4 - 14 Sep, 2015 ------------------------- * Trie-based GC-free dictionary class (`std.container.dict`) * Several performance optimizations in `dlib.math`: vector element access and multiplication for 3x3 and 4x4 matrices are now faster * Fixed some 64-bit issues. dlib 0.6.3 - 17 Aug, 2015 ------------------------- * Fixed `dlib.filesystem.stdfs` compilation under 64-bit systems * Fixed PNG exporter bug with encoding non-compressible images * Added basic drawing functions (`dlib.image.render.shapes`) dlib 0.6.2 - 20 Jul, 2015 ------------------------- Bugfix release. * Removed coordinates clamping on pixel write in dlib.image * Fixed bug with PNG vertical flipping dlib 0.6.1 - 6 Jul, 2015 ------------------------ * Added memory profiler * Fixed `dlib.math.sse` compilation on 64-bit systems dlib 0.6.0 - 24 Jun, 2015 ------------------------- * dlib.core * 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 * dlib.filesystem * Added GC-free implementations for FileSystem and file streams * dlib.image * dlib.image.unmanaged provides generalized GC-free Image class with corresponding factory function * 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 * dlib.math * New module dlib.math.combinatorics with factorial, hyperfactorial, permutation, combinations, lucas number and other functions * 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. * dlib.container * DynamicArray now supports indexing (as a syntactic sugar). dlib 0.5.3 - 5 May, 2015 ----------------------- * Added Protobuf-style varint implementation (dlib.coding.varint) * Streams are now ManuallyAllocatable * Triangle struct in dlib.geometry.triangle now has tangent vectors * Fixed unittest build failure (#59) dlib 0.5.2 - 25 Feb, 2015 ------------------------- * Automated vector type conversion (#57), modulo operator for vectors (#58) * Fixed warning in dlib.image.io.bmp (#56) dlib 0.5.1 - 21 Feb, 2015 ------------------------- Small bugfix release: * Fixed wrong module name in dlib.geometry.frustum * Updated license information dlib 0.5.0 - 20 Feb, 2015 ------------------------- * dlib.core * Added manual memory management support. dlib.core.memory provide memory allocators based on standard C malloc/free. They can allocate arrays, classes and structs * Added prototype-based OOP system for structs (dlib.core.oop) with support for multiple inheritance and parametric polymorphism * dlib.image * Image loaders are now GC-free * 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. * Image allocation is based on a factory interface that abstracts over GC or MMM * Improved support for indexed PNGs - added alpha channel support * dlib.container * Added GC-free dynamic array implementation (dlib.container.array) * BST and AArray now use manual memory management * dlib.math * Quaternion is now based on and interchangeable with Vector via incapsulation * Dual quaternion support (dlib.math.dualquaternion) * Fixed incorrect dual number `pow` implementation * dlib.geometry * Breaking change: Frustum plane normals are now pointing outside frustum. Also Frustum-AABB intersection API is changed * Fixed bugs in AABB and Plane dlib 0.4.1 - 30 Dec, 2014 ------------------------- * dlib.image * Baseline JPEG decoder (dlib.image.io.jpeg) * dlib.math * New matrix printer with proper alignment (a la Matlab) dlib 0.4.0 - 27 Oct, 2014 ------------------------- * dlib.filesystem * Platform-specific modules are now grouped by corresponding packages (dlib.filesystem.windows, dlib.filesystem.posix) * `findFiles` is now a free function and can be used with any `ReadOnlyFileSystem` * dlib.math * Implemented LU decomposition for matrices (dlib.math.decomposition) * `dlib.math.linear` is now `dlib.math.linsolve`. Added `solveLU`, a new LU-based direct solver * 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 * `dlib.math.utils` now uses `clamp` from latest Phobos if available * Removed deprecated functionality * dlib.core: * Moved container modules (bst, linkedlist, etc) from dlib.core to separate package dlib.container. Removed useless dlib.core.method * Overall: improved compatibility with DMD 2.067. dlib 0.3.3 - 31 Jul, 2014 ------------------------- Mainly bugfix release. Changes: * Fixed compilation with DMD 2.066 * Added dlib.geometry.frustum * Improved dlib.math.quaternion dlib 0.3.2 - 11 Jun, 2014 ------------------------- Bugfix release. The main improvement is fixed compilation with some versions of LDC. dlib 0.3.1 - 13 May, 2014 ------------------------- Bugfix release. Changes: * Improved dlib.image, added interpolated pixel reading * Added matrix addition and subtraction, tensorProduct now works with any matrix sizes * Addressed many bugs in dlib.image and dlib.math. dlib 0.3.0 - 25 Mar, 2014 ------------------------- * dlib.core * Added simple yet robust I/O streams (dlib.core.stream), which are completely Phobos-independent * dlib.filesystem * Abstract FS interface and it's implementations for Windows and POSIX filesystems * dlib.image * 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 * Pixel iteration now can be done with `row` and `col` ranges * Parallel filtering is now easy with dlib.image.parallel. You can add multithreading to your existing filter code with just a few changes * Added support for TGA and BMP formats (only loading for now) * All image format I/O is now stream-based * dlib.math * Breaking change: matrices in dlib.math.matrix are now column-major * 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]"`) * Overall improvements & bugfixes * Much saner DUB support, addressed some serious problems with building, added configuration for pre-compiling as a static library dlib 0.2.4 - 12 Dec, 2013 ------------------------- Bugfix release + added support for DMD 2.064 package modules. dlib 0.2.3 - 6 Dec, 2013 ------------------------ Bugfix release. Fixed issues with compiling on 64-bit systems. dlib 0.2.1 - 20 Nov, 2013 ------------------------- Bugfix release. dlib 0.2.0 - 11 Oct, 2013 ------------------------- * Added XML parser (alpha quality); * 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; * Updated dlib.math.dual. Vectors of dual numbers can now be created; * Added support for Hermite curves (dlib.geometry.hermite). dlib 0.1.2 - 18 Jul, 2013 ------------------------- * Renamed ColorRGBA and ColorRGBAf into Color4 and Color4f; * Added support for image convolution. There are several built-in kernels (Identity, BoxBlur, GaussianBlur, Sharpen, Emboss, EdgeEmboss, EdgeDetect, Laplace); * Added support for HSV color space; * Added Chroma Keying and Color Pass filters. dlib 0.1.1 - 13 Jul, 2013 ------------------------- Bugfix release. dlib 0.1.0 - 13 Jul, 2013 ------------------------- Initial release. 13 Jul, 2013 ------------ Project moved to GitHub. 2012-2013 --------- Early development on code.google.com. 28 Sep, 2012 ------------ Start as a public project. ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct This 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. 1. We do not tolerate obscene language, insulting, rude and/or disparaging messages, the use of sexualized, violent and otherwise offensive speech and imagery. 2. 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. 3. 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. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Guidelines #### Bug reporting Open 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. Note 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). Bugs that have not been reproduced or discussed for 6 months or longer are marked as `wontfix` and closed. We also use several other labels: * Breaking change. Self-descriptive: an improvement that breaks backward compatibility * Bug. A bug that should be fixed without API change * Enhancement. A non-breaking improvement of existing functionality (optimization or a new feature) * Missing. Appears when existing functionality is removed due to regressions and needs to be rewritten, or when some implementation is not complete * New functionality. Self-descriptive: a new functionality request. #### Bug fixing Open 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. #### Implementing new features Before 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. New code should at least: * work under Windows and POSIX systems and provide platform-agnostic API * support x86 and x86_64 targets * not rely on third party libraries other than system API * follow dlib's [code style](#code-style-and-standards) * 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 * 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 * 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). #### Code style and standards dlib follows [D style](https://dlang.org/dstyle.html). Essential rules are the following: * Use spaces instead of tabs. Each indentation level is 4 spaces * Opening curly bracket should be on a new line (Allman style) * Functions and variables should be in `camelCase` * Types, constants and enums should be in `PascalCase` * Module names should be in lowercase. All 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. Each D module should start with a Boost license block prepended with a copyright notice: ```d /* Copyright (c) 2026 Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ ``` #### Documenting It 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): ```d /** * * * Description: * * * Copyright: Your Name 2026. * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Your Name */ module dlib.something.something; ``` #### Testing It 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. It is recommended to add one `unittest` block per function, method, or class. Test block should be formatted in the following way: ```d /// unittest { } ``` ================================================ FILE: COPYING.md ================================================ Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Doxyfile ================================================ DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "dlib" PROJECT_NUMBER = 1.1 PROJECT_BRIEF = "Allocators, I/O streams, math, geometry, image and audio processing for D" PROJECT_LOGO = "logo/dlib-logo-doxygen.png" OUTPUT_DIRECTORY = "docs-doxygen" CREATE_SUBDIRS = YES CREATE_SUBDIRS_LEVEL = 8 ALLOW_UNICODE_NAMES = NO OUTPUT_LANGUAGE = English GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html GENERATE_HTMLHELP = YES CHM_FILE = "documentation.chm" INPUT = "dlib" RECURSIVE = YES ================================================ FILE: README.md ================================================ dlib logo dlib 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. dlib has no external dependencies aside D's standard library. dlib is created and maintained by [Timur Gafarov](https://github.com/gecko0307). [![GitHub Actions CI Status](https://github.com/gecko0307/dlib/workflows/CI/badge.svg)](https://github.com/gecko0307/dlib/actions?query=workflow%3ACI) [![DUB Package](https://img.shields.io/dub/v/dlib.svg)](https://code.dlang.org/packages/dlib) [![DUB Downloads](https://img.shields.io/dub/dm/dlib.svg)](https://code.dlang.org/packages/dlib) [![License](http://img.shields.io/badge/license-boost-blue.svg)](http://www.boost.org/LICENSE_1_0.txt) [![Codecov](https://codecov.io/gh/gecko0307/dlib/branch/master/graph/badge.svg)](https://codecov.io/gh/gecko0307/dlib) [![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) If 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! What's inside ------------- Currently dlib consists of the following packages: * [dlib.core](https://gecko0307.github.io/dlib/docs/dlib/core.html) - basic functionality used by other modules (memory management, streams, threads, etc.) * [dlib.container](https://gecko0307.github.io/dlib/docs/dlib/container.html) - generic data structures (GC-free dynamic and associative arrays and more) * [dlib.filesystem](https://gecko0307.github.io/dlib/docs/dlib/filesystem.html) - abstract FS interface and its implementations for Windows and POSIX filesystems * [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.) * [dlib.geometry](https://gecko0307.github.io/dlib/docs/dlib/geometry.html) - computational geometry (ray casting, primitives, intersection, etc.) * [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) * [dlib.audio](https://gecko0307.github.io/dlib/docs/dlib/audio.html) - sound processing (8 and 16 bits per sample, synthesizers, WAV export and import) * [dlib.network](https://gecko0307.github.io/dlib/docs/dlib/network.html) - networking and web functionality * [dlib.memory](https://gecko0307.github.io/dlib/docs/dlib/memory.html) - memory allocators * [dlib.text](https://gecko0307.github.io/dlib/docs/dlib/text.html) - text processing, GC-free strings, Unicode decoding and encoding * [dlib.serialization](https://gecko0307.github.io/dlib/docs/dlib/serialization.html) - data serialization (XML and JSON parsers) * [dlib.random](https://gecko0307.github.io/dlib/docs/dlib/random.html) - random number generation * [dlib.coding](https://gecko0307.github.io/dlib/docs/dlib/coding.html) - various data compression and coding algorithms * [dlib.concurrency](https://gecko0307.github.io/dlib/docs/dlib/concurrency.html) - a thread pool. Supported Compilers ------------------- dlib 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. Documentation ------------- HTML 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. * [Autogenerated documentation](https://gecko0307.github.io/dlib/docs/dlib.html) * [Wiki](https://github.com/gecko0307/dlib/wiki) * [Rationale](https://github.com/gecko0307/dlib/wiki/Rationale) * [FAQ](https://github.com/gecko0307/dlib/wiki/FAQ) * [Best Practices](https://github.com/gecko0307/dlib/wiki/Best-Practices) * [Contributing Guidelines](https://github.com/gecko0307/dlib/blob/master/CONTRIBUTING.md) * [Gitter chat room](https://gitter.im/gecko0307/dlib) License ------- Copyright (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). Sponsors -------- Jan Jurzitza (WebFreak), Daniel Laburthe, Rafał Ziemniewski, Kumar Sookram, Aleksandr Kovalev, Robert Georges, Rais Safiullin (SARFEX), Benas Cernevicius, Koichi Takio, Konstantin Menshikov. Users ----- * [Dagon](https://github.com/gecko0307/dagon) - 3D game engine for D * [Electronvolt](https://github.com/gecko0307/electronvolt) - work-in-progress first person puzzle game * [DagoBan](https://github.com/Timu5/dagoban) - Sokoban clone * [DlangUI](https://github.com/buggins/dlangui) - native UI toolkit for D * [rengfx](https://github.com/xdrie/rengfx) - game engine based on raylib * [Voxelman](https://github.com/MrSmith33/voxelman) - voxel-based game engine * [RIP](https://github.com/LightHouseSoftware/rip) - image processing and analysis library by LightHouse Software * [GeneticAlgorithm](https://github.com/Hnatekmar/GeneticAlgorithm) - genetic algorithms library * [Orb](https://github.com/claudemr/orb) - a game/engine with procedural content * [Leptbag](https://github.com/thotgamma/LeptbagCpp) - physics simulator by Gamma-Lab. Written in C++, but supports D plugins * [aoynthesizer](https://github.com/AODQ/aoynthesizer) - sound synthesizer based on Lisp-like scripting language * [D-VXLMapPreview](https://github.com/rakiru/D-VXLMapPreview) - isometric preview generator for Ace of Spades and Iceball maps * [SMSFontConverter](https://github.com/Doom2fan/SMSFontConverter) - a program for generating Sega Master System fonts * [sacengine](https://github.com/tg-2/sacengine/) - engine reimplementation for the game Sacrifice * [Shaders Playground](https://github.com/AntonC9018/shader_playground) - a sandbox project for experimenting with OpenGL shaders * [wgpu-dlang](https://github.com/gecko0307/wgpu-dlang) - WebGPU usage example. ================================================ FILE: dlib/audio/io/package.d ================================================ /* Copyright (c) 2022-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Timur Gafarov 2022-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.audio.io; import std.path: extension; import dlib.audio.sound; public { import dlib.audio.io.wav; } class SoundLoadException: Exception { this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { super(msg, file, line, next); } } /** * Saves a sound to file, selects encoder by filename extension */ void saveSound(GenericSound snd, string filename) { switch(filename.extension) { case ".wav", ".WAV": snd.saveWAV(filename); break; default: assert(0, "Sound I/O error: unsupported sound format or illegal extension"); } } /** * Loads sound from a file, selects decoder by filename extension */ GenericSound loadSound(string filename) { switch(filename.extension) { case ".wav", ".WAV": return loadWAV(filename); default: assert(0, "Sound I/O error: unsupported sound format or illegal extension"); } } ================================================ FILE: dlib/audio/io/wav.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Uncompressed RIFF/WAV encoder and decoder * * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.audio.io.wav; //version = WAVDebug; // Uncomment to see debug messages version(WAVDebug) { import std.stdio; } import dlib.core.memory; import dlib.core.stream; import dlib.filesystem.local; import dlib.audio.sample; import dlib.audio.sound; /** * Simple RIFF/WAV decoder. Decodes WAV from stream using provided sound factory */ GenericSound loadWAV(InputStream istrm, GenericSoundFactory gsf) { char[4] magic; istrm.fillArray(magic); assert(magic == "RIFF"); int chunkSize; istrm.readLE(&chunkSize); version(WAVDebug) { writeln(chunkSize); } char[4] format; istrm.fillArray(format); version(WAVDebug) { writeln(format); } assert(format == "WAVE"); char[4] fmtSubchunkID; // fmt istrm.fillArray(fmtSubchunkID); int fmtSubchunkSize; istrm.readLE(&fmtSubchunkSize); short audioFormat; short numChannels; int sampleRate; int byteRate; short blockAlign; short bitsPerSample; istrm.readLE(&audioFormat); istrm.readLE(&numChannels); istrm.readLE(&sampleRate); istrm.readLE(&byteRate); istrm.readLE(&blockAlign); istrm.readLE(&bitsPerSample); version(WAVDebug) { writefln("Format: %s", audioFormat); writefln("Number of channels: %s", numChannels); writefln("Sample rate: %s Hz", sampleRate); writefln("Byte rate: %s", byteRate); writefln("Block align: %s", blockAlign); writefln("Bits per sample: %s", bitsPerSample); } char[4] dataSubchunkID; // data istrm.fillArray(dataSubchunkID); int dataSubchunkSize; istrm.readLE(&dataSubchunkSize); version(WAVDebug) { writeln(dataSubchunkSize); } int numSamples = dataSubchunkSize / (numChannels * bitsPerSample/8); version(WAVDebug) { writefln("Number of samples: %s", numSamples); writefln("Duration: %s", (cast(float)numSamples) / sampleRate); } SampleFormat f; if (bitsPerSample == 8) { f = SampleFormat.U8; } else if (bitsPerSample == 16) { f = SampleFormat.S16; } GenericSound gs; gs = gsf.createSound( dataSubchunkSize, numSamples, (cast(double)numSamples) / sampleRate, numChannels, sampleRate, bitsPerSample, f ); istrm.fillArray(gs.data); return gs; } /** * Decodes WAV from stream using default sound factory */ GenericSound loadWAV(InputStream istrm) { return loadWAV(istrm, defaultGenericSoundFactory); } /** * Decodes WAV from file */ GenericSound loadWAV(string filename) { auto istrm = openForInput(filename); ubyte[] data = New!(ubyte[])(cast(size_t)istrm.size); istrm.fillArray(data); ArrayStream arrStrm = New!ArrayStream(data); auto snd = loadWAV(arrStrm); Delete(arrStrm); Delete(data); istrm.close(); return snd; } /** * Simple RIFF/WAV encoder. Encodes WAV to stream */ void saveWAV(Sound snd, OutputStream ostrm) { string magic = "RIFF"; ostrm.writeBytes(magic.ptr, 4); int chunkSize = 36 + cast(int)snd.data.length; // total file size - 8 ostrm.writeLE(chunkSize); string format = "WAVE"; ostrm.writeBytes(format.ptr, 4); string fmt = "fmt "; ostrm.writeBytes(fmt.ptr, 4); int fmtSubchunkSize = 16; ostrm.writeLE(fmtSubchunkSize); short audioFormat = 1; short numChannels = cast(short)snd.channels; assert(numChannels == 1 || numChannels == 2); int sampleRate = snd.sampleRate; // samples per second int byteRate = snd.sampleRate * snd.sampleSize; // bytes per second short blockAlign = cast(short)snd.sampleSize; // bytes per sample short bitsPerSample; if (snd.sampleFormat == SampleFormat.U8) bitsPerSample = 8; else if (snd.sampleFormat == SampleFormat.S16) bitsPerSample = 16; else { assert(0, "Illegal sample format to use with RIFF/WAV"); } ostrm.writeLE(audioFormat); ostrm.writeLE(numChannels); ostrm.writeLE(sampleRate); ostrm.writeLE(byteRate); ostrm.writeLE(blockAlign); ostrm.writeLE(bitsPerSample); string dataID = "data"; ostrm.writeBytes(dataID.ptr, 4); int dataSubchunkSize = cast(int)snd.data.length; ostrm.writeLE(dataSubchunkSize); ostrm.writeArray(snd.data); } /** * Encodes WAV to file */ void saveWAV(Sound snd, string filename) { auto ostrm = openForOutput(filename); saveWAV(snd, ostrm); ostrm.close(); } ================================================ FILE: dlib/audio/package.d ================================================ /* Copyright (c) 2016-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * This package provides basic tools for sound processing. It currently supports * bit depths 8 and 16 (signed and unsigned), as well as arbitrary sample rate and * number of channels. Design principles of dlib.audio are closely akin to dlib.image. * * Copyright: Timur Gafarov 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.audio; public { import dlib.audio.sample; import dlib.audio.sound; import dlib.audio.synth; import dlib.audio.unmanaged; import dlib.audio.io; import dlib.audio.io.wav; } ================================================ FILE: dlib/audio/sample.d ================================================ /* Copyright (c) 2016-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Sample types and conversion utilities * * Copyright: Timur Gafarov 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.audio.sample; /** * dlib.audio defines four integer sample formats: signed 8-bit, signed 16-bit, * unsigned 8-bit, unsigned 16-bit. * They are for storage only. All sound processing and sample I/O * should be done in floating point numbers. Floating point sample is signed and * ranges from -1.0f to 1.0f. */ enum SampleFormat { /// signed 8-bit S8, /// signed 16-bit S16, /// unsigned 8-bit U8, /// unsigned 16-bit U16 } /** * Convert integer sample to floating point sample */ float toFloatSample(ubyte* ptr, SampleFormat format) { float res; switch (format) { case SampleFormat.S8: res = *cast(byte*)ptr; res /= byte.max; break; case SampleFormat.S16: res = *cast(short*)ptr; res /= short.max; break; case SampleFormat.U8: res = *ptr; res /= ubyte.max; res = res * 2.0f - 1.0f; // normalize to range -1..1 break; case SampleFormat.U16: res = *cast(ushort*)ptr; res /= ushort.max; res = res * 2.0f - 1.0f; // normalize to range -1..1 break; default: break; } return res; } /** * Convert floating point sample to integer sample */ void fromFloatSample(ubyte* ptr, SampleFormat format, float s) { switch (format) { case SampleFormat.S8: *cast(byte*)ptr = cast(byte)(s * byte.max); break; case SampleFormat.S16: *cast(short*)ptr = cast(short)(s * short.max); break; case SampleFormat.U8: *cast(ubyte*)ptr = cast(ubyte)((s + 1.0f) * 0.5f * ubyte.max); break; case SampleFormat.U16: *cast(ushort*)ptr = cast(ushort)((s + 1.0f) * 0.5f * ushort.max); break; default: break; } } ================================================ FILE: dlib/audio/sound.d ================================================ /* Copyright (c) 2016-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Generic sound interfaces and their implementations * * Copyright: Timur Gafarov 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.audio.sound; import std.math; import dlib.audio.sample; /** * Generalized sound stream class. * This can be used to implement any type of sound, * including compressed audio streams. */ abstract class StreamedSound { /// Number of channels per sample @property uint channels(); /// Number of samples per second, in Hz @property uint sampleRate(); /// Number of bits in each sample channel @property uint bitDepth(); /// Number of bytes in each sample (bitDepth / 8 * channels) @property uint sampleSize() { return (bitDepth / 8) * channels; } /// Sample format @property SampleFormat sampleFormat(); /// Fill in the buffer with next portion of sound data. /// Function returns actual number of bytes written. /// At the end of a stream, zero is returned. ulong stream(ubyte[] buffer); /// Return to the start of a stream void reset(); } /** * Sound that can be seeked */ abstract class SeekableSound: StreamedSound { /// Number of samples @property ulong size(); /// Duration, in seconds @property double duration(); float opIndex(uint chan, ulong pos); float opIndexAssign(float sample, uint chan, ulong pos); } /** * Sound that is fully kept in memory */ abstract class Sound: SeekableSound { /// Raw byte data @property ref ubyte[] data(); /// Make exact copy of the sound @property Sound dup(); /// Make empty sound of the same format Sound createSameFormat(uint ch, double dur); } /** * Generic Sound implementation */ class GenericSound: Sound { protected: ubyte[] _data; ulong _size; double _duration; uint _channels; uint _sampleRate; uint _bitDepth; SampleFormat _format; public: this(Sound ras) { auto rasData = ras.data; allocateData(rasData.length); for(size_t i = 0; i < _data.length; i++) _data[i] = rasData[i]; _size = ras.size; _duration = ras.duration; _channels = ras.channels; _sampleRate = ras.sampleRate; _bitDepth = ras.bitDepth; _format = ras.sampleFormat; } this(double dur, uint freq, uint numChannels, SampleFormat f) { _duration = dur; _sampleRate = freq; _channels = numChannels; _size = cast(ulong)ceil(dur * freq); _format = f; if (f == SampleFormat.U8 || f == SampleFormat.S8) _bitDepth = 8; else if (f == SampleFormat.U16 || f == SampleFormat.S16) _bitDepth = 16; size_t dataSize = cast(size_t)(_size * (numChannels * (_bitDepth / 8))); allocateData(dataSize); } this(size_t dataSize, ulong numSamples, double dur, uint numChannels, uint freq, uint bitdepth, SampleFormat f) { allocateData(dataSize); _size = numSamples; _duration = dur; _channels = numChannels; _sampleRate = freq; _bitDepth = bitdepth; _format = f; } override @property ulong size() { return _size; } override @property double duration() { return _duration; } override @property uint channels() { return _channels; } override @property uint sampleRate() { return _sampleRate; } override @property uint bitDepth() { return _bitDepth; } override @property SampleFormat sampleFormat() { return _format; } override @property ref ubyte[] data() { return _data; } protected: size_t position; // sample position protected void allocateData(size_t size) { _data = new ubyte[size]; } public: override ulong stream(ubyte[] buffer) { size_t ssize = sampleSize(); size_t bytePos = position * ssize; size_t sizeInBytes = cast(size_t)_size * ssize; size_t bytesWritten = buffer.length; if (bytePos + buffer.length >= sizeInBytes) bytesWritten = sizeInBytes - bytePos; for(size_t i = bytePos; i < bytePos + bytesWritten; i++) { buffer[i] = _data[i]; } return bytesWritten; } override void reset() { position = 0; } override float opIndex(uint chan, ulong pos) { size_t ssize = sampleSize(); uint sampleChannelSize = _bitDepth / 8; ubyte* samplePtr = &_data[cast(size_t)pos * ssize] + chan * sampleChannelSize; return toFloatSample(samplePtr, _format); } override float opIndexAssign(float s, uint chan, ulong pos) { size_t ssize = sampleSize(); uint sampleChannelSize = _bitDepth / 8; ubyte* samplePtr = &_data[cast(size_t)pos * ssize] + chan * sampleChannelSize; fromFloatSample(samplePtr, _format, s); return s; } override @property Sound dup() { return new GenericSound(this); } override Sound createSameFormat(uint ch, double dur) { return new GenericSound(dur, _sampleRate, ch, _format); } } /** * GenericSound object factory */ interface GenericSoundFactory { GenericSound createSound( size_t dataSize, ulong numSamples, double dur, uint numChannels, uint freq, uint bitdepth, SampleFormat f); } /** * GenericSoundFactory implementation for Sound class */ class DefaultGenericSoundFactory: GenericSoundFactory { GenericSound createSound( size_t dataSize, ulong numSamples, double dur, uint numChannels, uint freq, uint bitdepth, SampleFormat f) { return new GenericSound( dataSize, numSamples, dur, numChannels, freq, bitdepth, f ); } } private __gshared DefaultGenericSoundFactory _defaultGenericSoundFactory; /** * GenericSoundFactory singleton */ DefaultGenericSoundFactory defaultGenericSoundFactory() { if (!_defaultGenericSoundFactory) _defaultGenericSoundFactory = new DefaultGenericSoundFactory(); return _defaultGenericSoundFactory; } ================================================ FILE: dlib/audio/synth.d ================================================ /* Copyright (c) 2016-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Simplest waveform synthesizers * * Copyright: Timur Gafarov 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.audio.synth; import std.math; import std.random; import dlib.audio.sound; /** * An interface for a synthesizer that maps sample position to -1..1 sample value */ interface Synth { /** * Evaluate a sample. * * Params: * sound = a sound object which parameters are used to discretize a sample * position = sample index * frequency = signal frequency in Hz */ float eval(Sound sound, ulong position, float frequency); } /** * Sine wave synthesizer */ class SineWaveSynth: Synth { /** * Evaluate a sine wave sample. * * Params: * sound = a sound object which parameters are used to discretize a sample * position = sample index * frequency = signal frequency in Hz */ float eval(Sound sound, ulong position, float frequency) { double samplePeriod = 1.0 / cast(double)sound.sampleRate; double time = position * samplePeriod; return sin(2.0 * PI * frequency * time); } } /** * Square wave synthesizer */ class SquareWaveSynth: Synth { /** * Evaluate square wave sample. * * Params: * sound = a sound object which parameters are used to discretize a sample * position = sample index * frequency = signal frequency in Hz */ float eval(Sound sound, ulong position, float frequency) { double samplePeriod = 1.0 / cast(double)sound.sampleRate; double phase = position * samplePeriod * frequency; double s = 2.0 * floor(phase) - floor(2.0 * phase) + 1.0; return s * 2.0 - 1.0; } } /** * Sawtooth wave synthesizer */ class SawtoothWaveSynth: Synth { /** * Evaluate sawtooth wave sample. * * Params: * sound = a sound object which parameters are used to discretize a sample * position = sample index * frequency = signal frequency in Hz */ float eval(Sound sound, ulong position, float frequency) { double samplePeriod = 1.0 / cast(double)sound.sampleRate; double phase = position * samplePeriod * frequency; double s = phase - floor(phase); return s * 2.0 - 1.0; } } /** * Triangle wave synthesizer */ class TriangleWaveSynth: Synth { /** * Evaluate triangle wave sample. * * Params: * sound = a sound object which parameters are used to discretize a sample * position = sample index * frequency = signal frequency in Hz */ float eval(Sound sound, ulong position, float frequency) { double samplePeriod = 1.0 / cast(double)sound.sampleRate; double phase = position * samplePeriod * frequency; double s = abs(1.0 - fmod(phase, 2.0)); return s * 2.0 - 1.0; } } /** * Frequency modulation synthesizer */ class FMSynth: Synth { Synth carrier; Synth modulator; float frequencyRatio; /** * Constructor. * * Params: * carrier = carrier waveform * modulator = modulator waveform * frequencyRatio = frequency multiplier for modulator */ this(Synth carrier, Synth modulator, float frequencyRatio) { this.carrier = carrier; this.modulator = modulator; this.frequencyRatio = frequencyRatio; } /** * Evaluate FM sample. * * Params: * sound = a sound object which parameters are used to discretize a sample * position = sample index * frequency = signal frequency in Hz */ float eval(Sound sound, ulong position, float frequency) { float m = modulator.eval(sound, position, frequency * frequencyRatio); return carrier.eval(sound, position, frequency + m); } } /** * Fill a given portion of a sound with a signal from specified synthesizer. * Params: * sound = a sound object to write to * channel = channel to fill (beginning from 0) * synth = synthesizer object * freq = synthesizer frequency * startTime = start time of a signal in seconds * duration = duration of a signal in seconds * amplitude = volume coefficient of a signal */ void fillSynth(Sound sound, uint channel, Synth synth, float freq, double startTime, double duration, float amplitude) { ulong startSample = cast(ulong)(startTime * sound.sampleRate); ulong endSample = startSample + cast(ulong)(duration * sound.sampleRate); if (endSample >= sound.size) endSample = sound.size - 1; foreach(i; startSample..endSample) { sound[channel, i] = synth.eval(sound, i - startSample, freq) * amplitude; } } /** * Additively mix a signal from specified synthesizer to a given portion of a sound. * sound = a sound object to write to * channel = channel to fill (beginning from 0) * synth = synthesizer object * freq = synthesizer frequency * startTime = start time of a signal in seconds * duration = duration of a signal in seconds * amplitude = volume coefficient of a signal */ void mixSynth(Sound sound, uint channel, Synth synth, float freq, double startTime, double duration, float amplitude) { ulong startSample = cast(ulong)(startTime * sound.sampleRate); ulong endSample = startSample + cast(ulong)(duration * sound.sampleRate); if (endSample >= sound.size) endSample = sound.size - 1; foreach(i; startSample..endSample) { float src = sound[channel, i]; sound[channel, i] = src + synth.eval(sound, i - startSample, freq) * amplitude; } } /** * Generate random audio signal. * snd = sound * ch = channel to fill (beginning from 0) */ void whiteNoise(Sound snd, uint ch) { foreach(i; 0..snd.size) { snd[ch, i] = uniform(-1.0f, 1.0f); } } /** * Fill the sound with simple sine wave tone. * snd = sound * ch = channel to fill (beginning from 0) * freq = frequency in Hz. For example, a dial tone in Europe is usually 425 Hz */ void sineWave(Sound snd, uint ch, float freq) { float samplePeriod = 1.0f / cast(float)snd.sampleRate; foreach(i; 0..snd.size) { snd[ch, i] = sin(samplePeriod * i * freq * 2.0f * PI); } } ================================================ FILE: dlib/audio/unmanaged.d ================================================ /* Copyright (c) 2016-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * GC-free sound class * * Copyright: Timur Gafarov 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.audio.unmanaged; import dlib.core.memory; import dlib.audio.sample; import dlib.audio.sound; /** * An implementation of GenericSound that doesn't use garbage collector */ class UnmanagedGenericSound: GenericSound { this(Sound ras) { super(ras); } this(double dur, uint freq, uint numChannels, SampleFormat f) { super(dur, freq, numChannels, f); } this(size_t dataSize, ulong numSamples, double dur, uint numChannels, uint freq, uint bitdepth, SampleFormat f) { super(dataSize, numSamples, dur, numChannels, freq, bitdepth, f); } protected override void allocateData(size_t size) { _data = New!(ubyte[])(size); } override @property Sound dup() { return New!UnmanagedGenericSound(this); } override Sound createSameFormat(uint ch, double dur) { return New!UnmanagedGenericSound(dur, _sampleRate, ch, _format); } ~this() { if (_data) Delete(_data); } } /** * GenericSoundFactory implementation for UnmanagedGenericSound class */ class UnmanagedGenericSoundFactory: GenericSoundFactory { UnmanagedGenericSound createSound( size_t dataSize, ulong numSamples, double dur, uint numChannels, uint freq, uint bitdepth, SampleFormat f) { return New!UnmanagedGenericSound( dataSize, numSamples, dur, numChannels, freq, bitdepth, f ); } } ================================================ FILE: dlib/coding/package.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Data coding and compression algorithms * * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.coding; public { import dlib.coding.zlib; import dlib.coding.varint; } ================================================ FILE: dlib/coding/varint.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Variable-sized integer type * * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.coding.varint; /** * Protobuf-style variable-sized integer */ struct Varint { ubyte[8] buffer; size_t size; /// Returns bytes of an integer ubyte[] bytes() return { return buffer[0..size]; } } /** * Returns size in bytes necessary to store an integer */ size_t getVarintSize(ulong n) { enum ulong N1 = 128; enum ulong N2 = 16384; enum ulong N3 = 2097152; enum ulong N4 = 268435456; enum ulong N5 = 34359738368; enum ulong N6 = 4398046511104; enum ulong N7 = 562949953421312; enum ulong N8 = 72057594037927936; return ( n < N1 ? 1 : n < N2 ? 2 : n < N3 ? 3 : n < N4 ? 4 : n < N5 ? 5 : n < N6 ? 6 : n < N7 ? 7 : 8 ); } /// unittest { assert(getVarintSize(4637734) == 4); } /** * Encode a fixed-size integer to Varint */ Varint encodeVarint(ulong n) { Varint res; res.size = getVarintSize(n); ubyte* ptr = res.buffer.ptr; for (uint i = 0; i < res.size; i++) { if (i == res.size - 1) *(ptr++) = n & 0xFF; else *(ptr++) = cast(ubyte)(n | 0x80); n >>= 7; } return res; } /// unittest { Varint v = encodeVarint(4783); assert(v.size == 2); assert(v.bytes == [175, 37]); } /** * Decode Varing to fixed-size integer */ ulong decodeVarint(Varint vint) { ulong result = 0; int bits = 0; ubyte* ptr = vint.buffer.ptr; ulong ll; while (*ptr & 0x80) { ll = *ptr; result += ((ll & 0x7F) << bits); ptr++; bits += 7; } ll = *ptr; result += ((ll & 0x7F) << bits); return result; } /// unittest { Varint v = encodeVarint(2345); assert(decodeVarint(v) == 2345); } ================================================ FILE: dlib/coding/zlib.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Zlib compression and decompression * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.coding.zlib; import etc.c.zlib; import dlib.core.memory; /** * Zlib encoder that uses provided fixed-length buffer to store results. * Doesn't use garbage collector */ struct ZlibBufferedEncoder { z_stream zlibStream; ubyte[] buffer; ubyte[] input; bool ended = true; /// Constructor this(ubyte[] buf, ubyte[] inp) { buffer = buf; input = inp; zlibStream.next_out = buffer.ptr; zlibStream.avail_out = cast(uint)buffer.length; zlibStream.data_type = Z_BINARY; zlibStream.zalloc = null; zlibStream.zfree = null; zlibStream.opaque = null; zlibStream.next_in = inp.ptr; zlibStream.avail_in = cast(uint)inp.length; deflateInit(&zlibStream, Z_BEST_COMPRESSION); ended = false; } /// Encodes the next portion of data and returns a number of bytes written size_t encode() { zlibStream.next_out = buffer.ptr; zlibStream.avail_out = cast(uint)buffer.length; zlibStream.total_out = 0; while (zlibStream.avail_out > 0) { int msg = deflate(&zlibStream, Z_FINISH); if (msg == Z_STREAM_END) { deflateEnd(&zlibStream); ended = true; return zlibStream.total_out; } else if (msg != Z_OK) { deflateEnd(&zlibStream); return 0; } } return zlibStream.total_out; } } /** * Zlib decoder that uses provided buffer to store results * Doesn't use garbage collector */ struct ZlibDecoder { z_stream zlibStream; ubyte[] buffer; int msg = 0; bool isInitialized = false; bool hasEnded = false; /// Constructor this(ubyte[] buf) { buffer = buf; zlibStream.next_out = buffer.ptr; zlibStream.avail_out = cast(uint)buffer.length; zlibStream.data_type = Z_BINARY; } /** * Decodes a stream. Returns true on success, false on error. * Decoded data is stored in buffer field, the buffer is resized if necessary */ bool decode(ubyte[] input) { zlibStream.next_in = input.ptr; zlibStream.avail_in = cast(uint)input.length; if (!isInitialized) { isInitialized = true; msg = inflateInit(&zlibStream); if (msg) { inflateEnd(&zlibStream); return false; } } while (zlibStream.avail_in) { msg = inflate(&zlibStream, Z_NO_FLUSH); if (msg == Z_STREAM_END) { inflateEnd(&zlibStream); hasEnded = true; reallocateBuffer(zlibStream.total_out); return true; } else if (msg != Z_OK) { inflateEnd(&zlibStream); return false; } else if (zlibStream.avail_out == 0) { reallocateBuffer(buffer.length * 2); zlibStream.next_out = &buffer[buffer.length / 2]; zlibStream.avail_out = cast(uint)(buffer.length / 2); } } return true; } void reallocateBuffer(size_t len) { ubyte[] buffer2 = New!(ubyte[])(len); for(uint i = 0; i < buffer2.length; i++) if (i < buffer.length) buffer2[i] = buffer[i]; Delete(buffer); buffer = buffer2; } /// Call this when you don't need decoded buffer anymore void free() { Delete(buffer); } } ================================================ FILE: dlib/concurrency/package.d ================================================ /* Copyright (c) 2019-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * A thread pool for running tasks in parallel * * Copyright: Timur Gafarov 2019-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.concurrency; public { import dlib.concurrency.threadpool; import dlib.concurrency.workerthread; import dlib.concurrency.taskqueue; } ================================================ FILE: dlib/concurrency/taskqueue.d ================================================ /* Copyright (c) 2019-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Timur Gafarov 2019-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.concurrency.taskqueue; import dlib.core.mutex; import dlib.container.array; import dlib.concurrency.workerthread; /** * State of a Task */ enum TaskState { Valid = 0, Invalid = 1 } /** * Task object that encapsulates a delegate */ struct Task { TaskState state = TaskState.Invalid; void delegate() func; void run() { if (func !is null) { func(); } } } /** * Asynchronous task queue */ class TaskQueue { protected: enum size_t MaxTasks = 64; Array!(Task, MaxTasks) tasks; Mutex mutex; public: /// Constructor this() { mutex.init(); } ~this() { tasks.free(); mutex.destroy(); } /// Number of queued tasks size_t count() { return tasks.length; } /// Add a task to queue bool enqueue(Task task) { if (tasks.length < MaxTasks) { mutex.lock(); tasks.insertFront(task); mutex.unlock(); return true; } else return false; } /// Remove a task from queue Task dequeue() { if (tasks.length) { mutex.lock(); Task t = tasks[tasks.length-1]; tasks.removeBack(1); mutex.unlock(); return t; } else return Task(TaskState.Invalid, null); } } ================================================ FILE: dlib/concurrency/threadpool.d ================================================ /* Copyright (c) 2019-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Timur Gafarov 2019-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.concurrency.threadpool; import std.functional; import dlib.core.memory; import dlib.core.mutex; import dlib.concurrency.workerthread; import dlib.concurrency.taskqueue; /** * An object that manages worker threads and runs tasks on them */ class ThreadPool { protected: uint maxThreads; WorkerThread[] workerThreads; TaskQueue taskQueue; bool running = true; Mutex mutex; public: /// Constructor this(uint maxThreads) { this.maxThreads = maxThreads; workerThreads = New!(WorkerThread[])(maxThreads); taskQueue = New!TaskQueue(); mutex.init(); foreach(i, ref t; workerThreads) { t = New!WorkerThread(i, this); t.start(); } } ~this() { mutex.lock(); running = false; mutex.unlock(); foreach(i, ref t; workerThreads) { t.join(); Delete(t); } Delete(taskQueue); Delete(workerThreads); mutex.destroy(); } /// Create a task from delegate Task submit(void delegate() taskDele) { Task task = Task(TaskState.Valid, taskDele); if (!taskQueue.enqueue(task)) { task.run(); } return task; } /// Create a task from function pointer Task submit(void function() taskFunc) { return submit(toDelegate(taskFunc)); } Task request() { return taskQueue.dequeue(); } bool isRunning() { return running; } /// Returns true if all tasks are finished bool tasksDone() { if (taskQueue.count == 0) { foreach(i, t; workerThreads) { if (t.busy) return false; } return true; } else return false; } } /* /// unittest { import std.stdio; int x = 0; int y = 0; void task1() { while(x < 100) x += 1; } void task2() { while(y < 100) y += 1; } ThreadPool threadPool = New!ThreadPool(2); threadPool.submit(&task1); threadPool.submit(&task2); while(!threadPool.tasksDone) {} if (x != 100) writeln(x); if (y != 100) writeln(y); assert(x == 100); assert(y == 100); Delete(threadPool); } */ ================================================ FILE: dlib/concurrency/workerthread.d ================================================ /* Copyright (c) 2019-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Timur Gafarov 2019-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.concurrency.workerthread; import dlib.core.thread; import dlib.concurrency.threadpool; import dlib.concurrency.taskqueue; /** * A thread that is created by ThreadPool */ class WorkerThread: Thread { size_t id; ThreadPool pool; protected bool _busy = false; /// Constructor this(size_t id, ThreadPool pool) { super(&threadFunc); this.id = id; this.pool = pool; } bool busy() { return _busy; } protected void threadFunc() { while(pool.isRunning) { Task task = pool.request(); if (task.state != TaskState.Invalid) { _busy = true; task.run(); _busy = false; } } } } ================================================ FILE: dlib/container/array.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Dynamic (expandable) array with random access * * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov, Roman Vlasov, Andrey Penechko, Eugene Wissner, Roman Chistokhodov, aferust, ijet */ module dlib.container.array; import std.traits; import dlib.core.memory; /** * GC-free dynamic array implementation. * Very efficient for small-sized arrays. */ struct Array(T, size_t chunkSize = 32) { private: T[chunkSize] staticStorage; T[] dynamicStorage; uint numChunks = 0; uint pos = 0; /** * Get pointer to stored data */ private T* storage() nothrow { if (numChunks == 0) return staticStorage.ptr; else return dynamicStorage.ptr; } private const(T)* readOnlyStorage() const nothrow { if (numChunks == 0) return staticStorage.ptr; else return dynamicStorage.ptr; } private void addChunk() { if (numChunks == 0) { dynamicStorage = New!(T[])(chunkSize); } else { reallocateArray( dynamicStorage, dynamicStorage.length + chunkSize); } numChunks++; } /// unittest { Array!int arr; scope(exit) arr.free(); assert(arr.length == 0); arr.addChunk(); assert(arr.length == 0); } public: /** * Returns true if the array uses dynamic memory. */ @property bool isDynamic() const { return dynamicStorage.length > 0; } /** * Preallocate memory without resizing. */ void reserve(const(size_t) amount) { if (amount > pos && amount > staticStorage.length) { if (numChunks == 0) { dynamicStorage = New!(T[])(amount); foreach(i, v; staticStorage) dynamicStorage[i] = v; } else { reallocateArray(dynamicStorage, amount); } numChunks = cast(uint)(amount / 32 + amount % 32); } } /// unittest { Array!int arr; arr.reserve(100); assert(arr.length == 0); assert(arr.dynamicStorage.length == 100); } /** * Resize array and initialize newly added elements with initValue. */ void resize(const(size_t) newLen, T initValue) { if (newLen > pos) { reserve(newLen); for(size_t i = pos; i < newLen; i++) { storage[i] = initValue; } } pos = cast(uint)newLen; } /// unittest { Array!int arr; arr.resize(100, 1); assert(arr.length == 100); assert(arr[50] == 1); } /** * Shift contents of array to the right. * It inreases the size of array by 1. * The first element becomes default initialized. */ void shiftRight() { insertBack(T.init); for(uint i = pos-1; i > 0; i--) { storage[i] = storage[i-1]; } } /// unittest { Array!int arr; scope(exit) arr.free(); arr.shiftRight(); assert(arr.length == 1); assert(arr[0] == int.init); arr[0] = 1; arr.insertBack([2,3]); arr.shiftRight(); assert(arr.length == 4); assert(arr[0] == 1); assert(arr[1] == 1); assert(arr[2] == 2); assert(arr[3] == 3); } /** * Shift contents of array to the left by n positions. * Does not change the size of array. * n of last elements becomes default initialized. */ void shiftLeft(const(uint) n) { for(uint i = 0; i < pos; i++) { if (n + i < pos) storage[i] = storage[n + i]; else storage[i] = T.init; } } /// unittest { Array!int arr; scope(exit) arr.free(); arr.shiftLeft(1); assert(arr.length == 0); arr.insertBack([1,2,3,4,5]); arr.shiftLeft(2); assert(arr.length == 5); assert(arr[0] == 3); assert(arr[1] == 4); assert(arr[2] == 5); assert(arr[3] == int.init); assert(arr[4] == int.init); } /** * Append single element c to the end. */ void insertBack(T c) { if (numChunks == 0) { staticStorage[pos] = c; pos++; if (pos == chunkSize) { addChunk(); foreach(i, ref v; dynamicStorage) v = staticStorage[i]; } } else { if (pos == dynamicStorage.length) addChunk(); dynamicStorage[pos] = c; pos++; } } /// unittest { Array!int arr; scope(exit) arr.free(); foreach(i; 0..16) { arr.insertBack(i); } assert(arr.length == 16); arr.insertBack(16); assert(arr.length == 17); assert(arr[16] == 16); } /** * Append element to the start. */ void insertFront(T c) { shiftRight(); storage[0] = c; } /// unittest { Array!int arr; scope(exit) arr.free(); arr.insertFront(1); arr.insertBack(2); arr.insertFront(0); assert(arr.data == [0,1,2]); } /** * Append all elements of slice s to the end. */ void insertBack(const(T)[] s) { foreach(c; s) insertBack(cast(T)c); } /// unittest { Array!int arr; scope(exit) arr.free(); arr.insertBack([1,2,3,4]); assert(arr.data == [1,2,3,4]); arr.insertBack([5,6,7,8]); assert(arr.data == [1,2,3,4,5,6,7,8]); } /** * Append all elements of slice s to the start. */ void insertFront(const(T)[] s) { foreach_reverse(c; s) insertFront(cast(T)c); } /// unittest { Array!int arr; scope(exit) arr.free(); arr.insertFront([5,6,7,8]); assert(arr.data == [5,6,7,8]); arr.insertFront([1,2,3,4]); assert(arr.data == [1,2,3,4,5,6,7,8]); } /** * Same as insertBack, but in operator form. */ auto opOpAssign(string op)(T c) if (op == "~") { insertBack(c); return this; } /// unittest { Array!int arr; scope(exit) arr.free(); arr ~= 1; arr ~= 2; assert(arr.data == [1,2]); } /** * Same as insertBack, but in operator form. */ auto opOpAssign(string op)(const(T)[] s) if (op == "~") { insertBack(s); return this; } /// unittest { Array!int arr; scope(exit) arr.free(); arr ~= [1,2,3]; assert(arr.data == [1,2,3]); } /** * Remove n of elements from the end. * Returns: number of removed elements. */ uint removeBack(const(uint) n) { if (pos == n) { pos = 0; return n; } else if (pos >= n) { pos -= n; return n; } else { uint res = pos; pos = 0; return res; } } /// unittest { Array!int arr; scope(exit) arr.free(); arr.insertBack([1,2,3]); assert(arr.removeBack(3) == 3); assert(arr.length == 0); arr.insertBack([1,2,3,4]); assert(arr.removeBack(2) == 2); assert(arr.data == [1,2]); assert(arr.removeBack(3) == 2); assert(arr.length == 0); } /** * Remove n of elements from the start. * Returns: number of removed elements. */ uint removeFront(const(uint) n) { if (pos == n) { pos = 0; return n; } else if (pos > n) { shiftLeft(n); pos -= n; return n; } else { uint res = pos; pos = 0; return res; } } /// unittest { Array!int arr; scope(exit) arr.free(); arr.insertBack([1,2,3]); assert(arr.removeFront(3) == 3); arr.insertBack([1,2,3,4]); assert(arr.removeFront(2) == 2); assert(arr.data == [3,4]); assert(arr.removeFront(3) == 2); assert(arr.length == 0); } /** * Inserts an element by a given index * (resizing an array and shifting elements). */ void insertKey(const(size_t) i, T v) { if (i < pos) { T* s = storage(); insertBack(T.init); for (size_t p = pos-1; p > i; p--) { s[p] = s[p-1]; } s[i] = v; } } unittest { Array!int arr; scope(exit) arr.free(); arr.insertBack([1, 4, 5]); arr.insertKey(1, 7); assert(arr.length == 4); assert(arr.data == [1, 7, 4, 5]); } /** * Removes an element by a given index. */ void removeKey(const(size_t) i) { if (i < pos) { T* s = storage(); for (size_t p = i+1; p <= pos; p++) { s[p-1] = s[p]; } pos--; } } unittest { Array!int arr; scope(exit) arr.free(); arr.insertBack([1, 4, 5]); arr.removeKey(1); assert(arr.length == 2); assert(arr.data == [1, 5]); } alias insertAt = insertKey; alias removeAt = removeKey; /** * If obj is in array, remove its first occurence and return true. * Otherwise do nothing and return false. */ bool removeFirst(T obj) { size_t index; bool found = false; for (size_t i = 0; i < data.length; i++) { T o = data[i]; static if (isArray!T) { if (o[] == obj[]) { index = i; found = true; break; } } else { if (o is obj) { index = i; found = true; break; } } } if (found) { removeKey(index); return true; } return false; } // For backward compatibility alias append = insertBack; alias appendLeft = insertFront; alias remove = removeBack; alias removeLeft = removeFront; /** * Get number of elements in array. */ size_t length() const nothrow { return pos; } alias opDollar = length; /// unittest { Array!int arr; scope(exit) arr.free(); arr.insertBack([1,2,3]); assert(arr.length == 3); } /** * Get slice of data */ T[] data() nothrow { return storage[0..pos]; } const(T)[] readOnlyData() const nothrow { return readOnlyStorage[0..pos]; } /// unittest { Array!(int,4) arr; scope(exit) arr.free(); foreach(i; 0..6) { arr.insertBack(i); } assert(arr.data == [0,1,2,3,4,5]); } /** * Access element by index. */ T opIndex(const(size_t) index) { return data[index]; } /** * Access by slice. */ T[] opSlice(const(size_t) start, const(size_t) end) { return data[start..end]; } /** * Set element t for index. */ T opIndexAssign(T t, const(size_t) index) { data[index] = t; return t; } /** * Iterating over array via foreach. */ int opApply(scope int delegate(size_t i, ref T) dg) { int result = 0; foreach(i, ref v; data) { result = dg(i, v); if (result) break; } return result; } /** * Iterating over array via foreach_reverse. */ int opApplyReverse(scope int delegate(size_t i, ref T) dg) { int result = 0; for(size_t i = length; i-- > 0; ) { result = dg(i, data[i]); if (result) break; } return result; } /// unittest { Array!(int,4) arr; scope(exit) arr.free(); int[4] values; arr.insertBack([1,2,3,4]); foreach(i, ref val; arr) { values[i] = val; if(values[i] == 4) { break; } } assert(values[] == arr.data); } /** * Iterating over array via foreach. */ int opApply(scope int delegate(ref T) dg) { int result = 0; foreach(i, ref v; data) { result = dg(v); if (result) break; } return 0; } /** * Iterating over array via foreach_reverse. */ int opApplyReverse(scope int delegate(ref T) dg) { int result = 0; for(size_t i = length; i-- > 0; ) { result = dg(data[i]); if (result) break; } return result; } /// unittest { Array!(int,4) arr; scope(exit) arr.free(); int[] values; arr.insertBack([1,2,3,4]); foreach(ref val; arr) { values ~= val; } assert(values[] == arr.data); } /** * Free dynamically allocated memory used by array. */ void free() { if (dynamicStorage.length) Delete(dynamicStorage); numChunks = 0; pos = 0; } } void reallocateArray(T)(ref T[] buffer, const(size_t) len) { T[] buffer2 = New!(T[])(len); for(uint i = 0; i < buffer2.length; i++) if (i < buffer.length) buffer2[i] = buffer[i]; Delete(buffer); buffer = buffer2; } /// unittest { auto arr = New!(int[])(3); arr[0] = 1; arr[1] = 2; arr[2] = 3; reallocateArray(arr, 2); assert(arr.length == 2); assert(arr[0] == 1); assert(arr[1] == 2); reallocateArray(arr, 4); assert(arr.length == 4); assert(arr[0] == 1); assert(arr[1] == 2); } ================================================ FILE: dlib/container/bst.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Binary search tree * * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov, Andrey Penechko */ module dlib.container.bst; import dlib.core.memory; /** * GC-free binary search tree implementation. */ class BST(T) { bool root; BST left = null; BST right = null; int key = 0; T value; this() { root = true; } this(int k, T v) { key = k; value = v; root = false; } ~this() { clear(); } void insert(int k, T v) { if (k < key) { if (left is null) left = allocate!(BST)(k, v); else left.insert(k, v); } else if (k > key) { if (right is null) right = allocate!(BST)(k, v); else right.insert(k, v); } else value = v; } BST find(int k) { if (k < key) { if (left !is null) return left.find(k); else return null; } else if (k > key) { if (right !is null) return right.find(k); else return null; } else return this; } protected BST findLeftMost() { if (left is null) return this; else return left.findLeftMost(); } void remove(int k, BST par = null) { if (k < key) { if (left !is null) left.remove(k, this); else return; } else if (k > key) { if (right !is null) right.remove(k, this); else return; } else { if (left !is null && right !is null) { auto m = right.findLeftMost(); key = m.key; value = m.value; right.remove(key, this); } else if (this == par.left) { par.left = (left !is null)? left : right; } else if (this == par.right) { par.right = (left !is null)? left : right; } } } void traverse(void function(int, T) func) { if (left !is null) left.traverse(func); if (!root) func(key, value); if (right !is null) right.traverse(func); } int opApply(scope int delegate(int, ref T) dg) { int result = 0; if (left !is null) { result = left.opApply(dg); if (result) return result; } if (!root) dg(key, value); if (right !is null) { result = right.opApply(dg); if (result) return result; } return result; } void clear() { if (left !is null) { left.clear(); deallocate(left); left = null; } if (right !is null) { right.clear(); deallocate(right); right = null; } } } ================================================ FILE: dlib/container/buffer.d ================================================ /* Copyright (c) 2016-2025 Eugene Wissner Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Eugene Wissner 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Eugene Wissner */ module dlib.container.buffer; import dlib.memory; version (unittest) { private int fillBuffer(ubyte[] buffer, in size_t size, int start = 0, int end = 10) @nogc pure nothrow in { assert(start < end); } do { auto numberRead = end - start; for (ubyte i; i < numberRead; ++i) { buffer[i] = cast(ubyte) (start + i); } return numberRead; } } /** * Interface for implemeting input/output buffers. */ interface Buffer { /** * Returns: The size of the internal buffer. */ @property size_t capacity() const @nogc @safe pure nothrow; /** * Returns: Data size. */ @property size_t length() const @nogc @safe pure nothrow; /** * Returns: Available space. */ @property size_t free() const @nogc @safe pure nothrow; /** * Params: * start = Start position. * end = End position. * * Returns: Array between $(D_PARAM start) and $(D_PARAM end). */ @property ubyte[] opSlice(size_t start, size_t end) in { assert(start <= end); assert(end <= length); } /** * Returns: Length of available data. */ @property size_t opDollar() const pure nothrow @safe @nogc; /** * Returns: Data chunk. */ @property ubyte[] opIndex(); } /** * Self-expanding buffer, that can be used with functions returning the number * of the read bytes. * * This buffer supports asynchronous reading. It means you can pass a new chunk * to an asynchronous read function during you are working with already * available data. But only one asynchronous call at a time is supported. Be * sure to call $(D_PSYMBOL ReadBuffer.clear()) before you append the result * of the pended asynchronous call. */ class ReadBuffer : Buffer { /// Internal buffer. protected ubyte[] buffer_; /// Filled buffer length. protected size_t length_; /// Start of available data. protected size_t start; /// Last position returned with $(D_KEYWORD []). protected size_t ring; /// Available space. protected immutable size_t minAvailable; /// Size by which the buffer will grow. protected immutable size_t blockSize; invariant { assert(length_ <= buffer_.length); assert(blockSize > 0); assert(minAvailable > 0); } /** * Creates a new read buffer. * * Params: * size = Initial buffer size and the size by which the buffer * will grow. * minAvailable = minimal size should be always available to fill. * So it will reallocate if $(D_INLINECODE * $(D_PSYMBOL free) < $(D_PARAM minAvailable) * ). */ this(size_t size = 8192, size_t minAvailable = 1024) { this.minAvailable = minAvailable; this.blockSize = size; defaultAllocator.resizeArray!ubyte(buffer_, size); } /** * Deallocates the internal buffer. */ ~this() { defaultAllocator.dispose(buffer_); } /// unittest { auto b = defaultAllocator.make!ReadBuffer; assert(b.capacity == 8192); assert(b.length == 0); defaultAllocator.dispose(b); } /** * Returns: The size of the internal buffer. */ @property size_t capacity() const @nogc @safe pure nothrow { return buffer_.length; } /** * Returns: Data size. */ @property size_t length() const @nogc @safe pure nothrow { return length_ - start; } /** * Clears the buffer. * * Returns: $(D_KEYWORD this). */ ReadBuffer clear() pure nothrow @safe @nogc { start = length_ = ring; return this; } /** * Returns: Available space. */ @property size_t free() const pure nothrow @safe @nogc { return length > ring ? capacity - length : capacity - ring; } /// unittest { auto b = defaultAllocator.make!ReadBuffer; size_t numberRead; // Fills the buffer with values 0..10 assert(b.free == b.blockSize); numberRead = fillBuffer(b[], b.free, 0, 10); b += numberRead; assert(b.free == b.blockSize - numberRead); b.clear(); assert(b.free == b.blockSize); defaultAllocator.dispose(b); } /** * Appends some data to the buffer. * * Params: * length = Number of the bytes read. * * Returns: $(D_KEYWORD this). */ ReadBuffer opOpAssign(string op)(size_t length) if (op == "+") { length_ += length; ring = start; return this; } /// unittest { auto b = defaultAllocator.make!ReadBuffer; size_t numberRead; ubyte[] result; // Fills the buffer with values 0..10 numberRead = fillBuffer(b[], b.free, 0, 10); b += numberRead; result = b[0..$]; assert(result[0] == 0); assert(result[1] == 1); assert(result[9] == 9); b.clear(); // It shouldn't overwrite, but append another 5 bytes to the buffer numberRead = fillBuffer(b[], b.free, 0, 10); b += numberRead; numberRead = fillBuffer(b[], b.free, 20, 25); b += numberRead; result = b[0..$]; assert(result[0] == 0); assert(result[1] == 1); assert(result[9] == 9); assert(result[10] == 20); assert(result[14] == 24); defaultAllocator.dispose(b); } /** * Returns: Length of available data. */ @property size_t opDollar() const pure nothrow @safe @nogc { return length; } /** * Params: * start = Start position. * end = End position. * * Returns: Array between $(D_PARAM start) and $(D_PARAM end). */ @property ubyte[] opSlice(size_t start, size_t end) pure nothrow @safe @nogc { return buffer_[this.start + start .. this.start + end]; } /** * Returns a free chunk of the buffer. * * Add ($(D_KEYWORD +=)) the number of the read bytes after using it. * * Returns: A free chunk of the buffer. */ ubyte[] opIndex() { if (start > 0) { auto ret = buffer_[0..start]; ring = 0; return ret; } else { if (capacity - length < minAvailable) { defaultAllocator.resizeArray!ubyte(buffer_, capacity + blockSize); } ring = length_; return buffer_[length_..$]; } } /// unittest { auto b = defaultAllocator.make!ReadBuffer; size_t numberRead; ubyte[] result; // Fills the buffer with values 0..10 numberRead = fillBuffer(b[], b.free, 0, 10); b += numberRead; assert(b.length == 10); result = b[0..$]; assert(result[0] == 0); assert(result[9] == 9); b.clear(); assert(b.length == 0); defaultAllocator.dispose(b); } } /** * Circular, self-expanding buffer with overflow support. Can be used with * functions returning returning the number of the transferred bytes. * * The buffer is optimized for situations where you read all the data from it * at once (without writing to it occasionally). It can become ineffective if * you permanently keep some data in the buffer and alternate writing and * reading, because it may allocate and move elements. */ class WriteBuffer : Buffer { /// Internal buffer. protected ubyte[] buffer_; /// Buffer start position. protected size_t start; /// Buffer ring area size. After this position begins buffer overflow area. protected size_t ring; /// Size by which the buffer will grow. protected immutable size_t blockSize; /// The position of the free area in the buffer. protected size_t position; invariant { assert(blockSize > 0); // position can refer to an element outside the buffer if the buffer is full. assert(position <= buffer_.length); } /** * Params: * size = Initial buffer size and the size by which the buffer * will grow. */ this(size_t size = 8192) { blockSize = size; ring = size - 1; defaultAllocator.resizeArray!ubyte(buffer_, size); } /** * Deallocates the internal buffer. */ ~this() { defaultAllocator.dispose(buffer_); } /** * Returns: The size of the internal buffer. */ @property size_t capacity() const @nogc @safe pure nothrow { return buffer_.length; } /** * Note that $(D_PSYMBOL length) doesn't return the real length of the data, * but only the array length that will be returned with $(D_PSYMBOL buffer) * next time. Be sure to call $(D_PSYMBOL buffer) and set $(D_KEYWORD +=) * until $(D_PSYMBOL length) returns 0. * * Returns: Data size. */ @property size_t length() const @nogc @safe pure nothrow { if (position > ring || position < start) // Buffer overflowed { return ring - start + 1; } else { return position - start; } } /** * Returns: Length of available data. */ @property size_t opDollar() const pure nothrow @safe @nogc { return length; } /// unittest { auto b = defaultAllocator.make!WriteBuffer(4); ubyte[3] buf = [48, 23, 255]; b ~= buf; assert(b.length == 3); b += 2; assert(b.length == 1); b ~= buf; assert(b.length == 2); b += 2; assert(b.length == 2); b ~= buf; assert(b.length == 5); b += b.length; assert(b.length == 0); defaultAllocator.dispose(b); } /** * Returns: Available space. */ @property size_t free() const @nogc @safe pure nothrow { return capacity - length; } /** * Appends data to the buffer. * * Params: * buffer = Buffer chunk got with $(D_PSYMBOL buffer). */ WriteBuffer opOpAssign(string op)(ubyte[] buffer) if (op == "~") { size_t end, start; if (position >= this.start && position <= ring) { auto afterRing = ring + 1; end = position + buffer.length; if (end > afterRing) { end = afterRing; } start = end - position; buffer_[position..end] = buffer[0..start]; if (end == afterRing) { position = this.start == 0 ? afterRing : 0; } else { position = end; } } // Check if we have some free space at the beginning if (start < buffer.length && position < this.start) { end = position + buffer.length - start; if (end > this.start) { end = this.start; } auto areaEnd = end - position + start; buffer_[position..end] = buffer[start..areaEnd]; position = end == this.start ? ring + 1 : end - position; start = areaEnd; } // And if we still haven't found any place, save the rest in the overflow area if (start < buffer.length) { end = position + buffer.length - start; if (end > capacity) { auto newSize = end / blockSize * blockSize + blockSize; defaultAllocator.resizeArray!ubyte(buffer_, newSize); } buffer_[position..end] = buffer[start..$]; position = end; if (this.start == 0) { ring = capacity - 1; } } return this; } /// unittest { auto b = defaultAllocator.make!WriteBuffer(4); ubyte[3] buf = [48, 23, 255]; b ~= buf; assert(b.capacity == 4); assert(b.buffer_[0] == 48 && b.buffer_[1] == 23 && b.buffer_[2] == 255); b += 2; b ~= buf; assert(b.capacity == 4); assert(b.buffer_[0] == 23 && b.buffer_[1] == 255 && b.buffer_[2] == 255 && b.buffer_[3] == 48); b += 2; b ~= buf; assert(b.capacity == 8); assert(b.buffer_[0] == 23 && b.buffer_[1] == 255 && b.buffer_[2] == 48 && b.buffer_[3] == 23 && b.buffer_[4] == 255); defaultAllocator.dispose(b); b = make!WriteBuffer(defaultAllocator, 2); b ~= buf; assert(b.start == 0); assert(b.capacity == 4); assert(b.ring == 3); assert(b.position == 3); defaultAllocator.dispose(b); } /** * Sets how many bytes were written. It will shrink the buffer * appropriately. Always set this property after calling * $(D_PSYMBOL buffer). * * Params: * length = Length of the written data. * * Returns: $(D_KEYWORD this). */ @property WriteBuffer opOpAssign(string op)(size_t length) pure nothrow @safe @nogc if (op == "+") in { assert(length <= this.length); } do { auto afterRing = ring + 1; auto oldStart = start; if (length <= 0) { return this; } else if (position <= afterRing) { start += length; if (start > 0 && position == afterRing) { position = oldStart; } } else { auto overflow = position - afterRing; if (overflow > length) { buffer_[start.. start + length] = buffer_[afterRing.. afterRing + length]; buffer_[afterRing.. afterRing + length] = buffer_[afterRing + length ..position]; position -= length; } else if (overflow == length) { buffer_[start.. start + overflow] = buffer_[afterRing..position]; position -= overflow; } else { buffer_[start.. start + overflow] = buffer_[afterRing..position]; position = overflow; } start += length; if (start == position) { if (position != afterRing) { position = 0; } start = 0; ring = capacity - 1; } } if (start > ring) { start = 0; } return this; } /// unittest { auto b = defaultAllocator.make!WriteBuffer; ubyte[6] buf = [23, 23, 255, 128, 127, 9]; b ~= buf; assert(b.length == 6); b += 2; assert(b.length == 4); b += 4; assert(b.length == 0); defaultAllocator.dispose(b); } /** * Returns a chunk with data. * * After calling it, set $(D_KEYWORD +=) to the length could be * written. * * $(D_PSYMBOL buffer) may return only part of the data. You may need * to call it (and set $(D_KEYWORD +=) several times until * $(D_PSYMBOL length) is 0). If all the data can be written, * maximally 3 calls are required. * * Returns: A chunk of data buffer. */ @property ubyte[] opSlice(size_t start, size_t end) pure nothrow @safe @nogc { immutable internStart = this.start + start; if (position > ring || position < start) // Buffer overflowed { return buffer_[this.start.. ring + 1 - length + end]; } else { return buffer_[this.start.. this.start + end]; } } /// unittest { auto b = defaultAllocator.make!WriteBuffer(6); ubyte[6] buf = [23, 23, 255, 128, 127, 9]; b ~= buf; assert(b[0..$] == buf[0..6]); b += 2; assert(b[0..$] == buf[2..6]); b ~= buf; assert(b[0..$] == buf[2..6]); b += b.length; assert(b[0..$] == buf[0..6]); b += b.length; defaultAllocator.dispose(b); } /** * After calling it, set $(D_KEYWORD +=) to the length could be * written. * * $(D_PSYMBOL buffer) may return only part of the data. You may need * to call it (and set $(D_KEYWORD +=) several times until * $(D_PSYMBOL length) is 0). If all the data can be written, * maximally 3 calls are required. * * Returns: A chunk of data buffer. */ @property ubyte[] opIndex() pure nothrow @safe @nogc { return opSlice(0, length); } } ================================================ FILE: dlib/container/dict.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Trie-based dictionary (associative array) that can use any type as a key * * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov, Andrey Penechko, Roman Chistokhodov, ijet */ module dlib.container.dict; import std.stdio; import std.traits; import std.format; import std.string; import dlib.core.memory; import dlib.container.array; size_t dataSize(T)(T v) { static if (is(T == class) || is(T == interface)) return (void*).sizeof; else static if (isArray!T) return v.length * (ForeachType!T).sizeof; else return T.sizeof; } auto byteRange(T)(T v) { struct R { T value; size_t size; size_t offset; this(T v) { value = v; size = dataSize(v); offset = 0; } @property bool empty() { return (offset >= size); } @property ubyte front() { ubyte* ptr; static if (isArray!T) { ptr = (cast(ubyte[])value).ptr;} else { ptr = cast(ubyte*)&value; } return ptr[offset]; } void popFront() { offset++; } } return R(v); } /** * Trie-based dictionary (associative array) that can use any type as a key. No hash functions are required. */ class Trie(T, K) { T value; K key; ubyte symbol; Array!(Trie!(T, K)) children; bool active = false; size_t length = 0; this() { } this(ubyte s) { symbol = s; } /// Set value by key. void set(K k, T v) { Trie!(T, K) current = this; foreach(s; byteRange(k)) { bool found = false; foreach(c; current.children) { if (c.symbol == s) { current = c; found = true; break; } } if (!found) { auto n = New!(Trie!(T, K))(s); current.children.append(n); current = n; } } if (current !is this) { current.value = v; current.key = k; if (!current.active) { current.active = true; length++; } } } /// Get value by key. Returns null if the element does not exist in trie. T* get(K k) { Trie!(T, K) current = this; foreach(ubyte s; byteRange(k)) { bool found = false; foreach(c; current.children) { if (c.symbol == s) { found = true; current = c; break; } } if (!found) return null; } if (current !is this) { if (current.active && current.key == k) { return ¤t.value; } } return null; } /// Remove element by key. void remove(K k) { Trie!(T, K) current = this; foreach(ubyte s; byteRange(k)) { bool found = false; foreach(c; current.children) { if (c.symbol == s) { found = true; current = c; break; } } if (!found) return; } if (current !is this) { if (current.active && current.key == k) { current.active = false; length--; } } } /// Get value by key. It's an error to access non-existing key. T opIndex(K k) { T* v = get(k); if (v !is null) return *v; else assert(0, format("Non-existing key in Trie.opIndex: %s", k)); } /// Set value by key T opIndexAssign(T v, K k) { set(k, v); return v; } /// T* opBinaryRight(string op)(K k) if (op == "in") { return get(k); } int opApply(scope int delegate(K, ref T) dg) { int result = 0; foreach(c; children) { if (c.active) result = dg(c.key, c.value); if (result) break; result = c.opApply(dg); if (result) break; } return result; } /// Remove all elements. void clear() { foreach(c; children) { Delete(c); } children.free(); length = 0; } ~this() { clear(); } /// Trie must be manually freed when it's no longer needed. void free() { Delete(this); } } /// Convenient alias alias Dict = Trie; /// Convenient function for dict creation. Dict!(T, K) dict(T, K)() { return New!(Dict!(T, K))(); } /// unittest { auto d = dict!(string, string)(); scope(exit) d.free(); d["Hell"] = "No"; d["Hello"] = "World"; d["Help"] = "Me"; d["Something"] = "Else"; assert(d["Hell"] == "No"); assert(d["Hello"] == "World"); assert(d["Help"] == "Me"); assert(d["Something"] == "Else"); assert("Held" !in d); assert(d.length == 4); string[string] elements; foreach(key, value; d) { elements[key] = value; } assert(elements["Hell"] == "No"); assert(elements["Hello"] == "World"); assert(elements["Help"] == "Me"); assert(elements["Something"] == "Else"); assert(elements.length == d.length); d["Something"] = "New"; assert(d["Something"] == "New"); d.remove("Hell"); assert(d.length == 3); assert(d.get("Hell") is null); d.clear(); assert(d.length == 0); assert("Hello" !in d); assert("Help" !in d); assert("Something" !in d); d["Held"] = "Fire"; assert(d["Held"] == "Fire"); auto di = dict!(string, int); scope(exit) di.free(); di[0xBEAF] = "BEAF"; di[0xDEADBEAF] = "DEADBEAF"; di[0xDEAD] = "DEAD"; assert(di[0xBEAF] == "BEAF"); assert(di[0xDEADBEAF] == "DEADBEAF"); assert(di[0xDEAD] == "DEAD"); } ================================================ FILE: dlib/container/linkedlist.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Singly linked list * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov, Andrey Penechko, Roman Chistokhodov, ijet */ module dlib.container.linkedlist; import dlib.core.memory; /** * Element of single linked list. */ struct LinkedListElement(T) { LinkedListElement!(T)* next = null; T datum; this(LinkedListElement!(T)* n) { next = n; datum = T.init; } } /** * GC-free single linked list implementation. */ struct LinkedList(T, bool ordered = true) { ///Head of the list. LinkedListElement!(T)* head = null; ///Tail of the list. LinkedListElement!(T)* tail = null; ///Number of elements in the list. size_t length = 0; /** * Check if list has no elements. */ @property bool empty() { return length == 0; } /// unittest { LinkedList!int list; assert(list.empty); } /** * Remove all elements and free used memory. */ void free() { LinkedListElement!(T)* element = head; while (element !is null) { auto e = element; element = element.next; Delete(e); } head = null; tail = null; length = 0; } /** * Iterating over list via foreach. */ int opApply(scope int delegate(size_t, ref T) dg) { int result = 0; uint index = 0; LinkedListElement!(T)* element = head; while (element !is null) { result = dg(index, element.datum); if (result) break; element = element.next; index++; } return result; } /// unittest { LinkedList!int list; scope(exit) list.free(); list.append(1); list.append(2); list.append(3); list.append(4); int[4] values; foreach(size_t i, ref int val; list) { values[i] = val; } assert(values[] == [1,2,3,4]); } /** * Iterating over list via foreach. */ int opApply(scope int delegate(ref T) dg) { int result = 0; LinkedListElement!(T)* element = head; while (element !is null) { result = dg(element.datum); if (result) break; element = element.next; } return result; } /// unittest { LinkedList!int list; scope(exit) list.free(); list.append(1); list.append(2); list.append(3); list.append(4); int[] values; foreach(ref int val; list) { values ~= val; } assert(values[] == [1,2,3,4]); } /** * Appen value v to the end. * Returns: Pointer to added list element. */ LinkedListElement!(T)* insertBack(T v) { length++; if (tail is null) { tail = New!(LinkedListElement!(T))(null); tail.datum = v; } else { tail.next = New!(LinkedListElement!(T))(null); tail.next.datum = v; tail = tail.next; } if (head is null) head = tail; return tail; } // Insert operator auto opCatAssign(T v) { insertBack(v); return this; } /// unittest { LinkedList!int list; scope(exit) list.free(); auto element = list.append(13); assert(element.datum == 13); assert(list.length == 1); element = list.append(42); assert(element.datum == 42); assert(list.length == 2); } /** * Insert value v after element. * Returns: Pointer to inserted element. * Note: element must be not null. */ LinkedListElement!(T)* insertAfter(LinkedListElement!(T)* element, T v) { length++; auto newElement = New!(LinkedListElement!(T))(null); newElement.datum = v; newElement.next = element.next; element.next = newElement; if (element is tail) tail = newElement; return newElement; } /// unittest { LinkedList!int list; scope(exit) list.free(); auto first = list.append(1); auto last = list.append(2); list.insertAfter(first, 3); assert(list.length == 3); auto arr = list.toArray(); assert(arr == [1,3,2]); Delete(arr); } /** * Insert value v at the beginning. */ LinkedListElement!(T)* insertFront(T v) { length++; auto newElement = New!(LinkedListElement!(T))(null); newElement.datum = v; newElement.next = head; head = newElement; if (tail is null) { tail = head; } return newElement; } /// unittest { LinkedList!int list; scope(exit) list.free(); list.insertBeginning(1); list.insertBack(2); list.insertBeginning(0); import std.algorithm : equal; assert(equal(list.byElement(), [0,1,2])); } /** * Remove value after element. * Note: element must be not null. */ void removeAfter(LinkedListElement!(T)* element) { length--; auto obsolete = element.next; if (obsolete !is null) { if (obsolete is tail) tail = element; element.next = obsolete.next; Delete(obsolete); } } /// unittest { LinkedList!int list; scope(exit) list.free(); auto first = list.insertBack(1); auto second = list.insertBack(2); auto third = list.insertBack(3); list.removeAfter(first); import std.algorithm : equal; assert(equal(list.byElement(), [1,3])); } /** * Remove the first element. * Note: list must be non-empty. */ void removeFront() { length--; auto obsolete = head; if (obsolete !is null) { head = obsolete.next; Delete(obsolete); } } /// unittest { LinkedList!int list; scope(exit) list.free(); list.insertBack(0); list.removeFront(); assert(list.length == 0); list.insertBack(1); list.insertBack(2); list.insertBack(3); list.removeFront(); assert(list.length == 2); import std.algorithm : equal; assert(equal(list.byElement(), [2,3])); } /** * Append other list. * Note: Appended list should not be freed. It becomes part of this list. */ void appendList(LinkedList!(T) list) { length += list.length; if (tail !is null) { tail.next = list.head; } if (head is null) { head = list.head; } tail = list.tail; } /// unittest { LinkedList!int list1; scope(exit) list1.free(); LinkedList!int list2; LinkedList!int list3; list2.insertBack(1); list2.insertBack(2); list1.appendList(list2); import std.algorithm : equal; assert(equal(list1.byElement(), [1,2])); list3.insertBack(3); list3.insertBack(4); list1.appendList(list3); assert(equal(list1.byElement(), [1,2,3,4])); } /** * Search for element with value v. * Returns: Found element or null if could not find. */ LinkedListElement!(T)* find(T v) { LinkedListElement!(T)* element = head; LinkedListElement!(T)* prevElement = head; while (element !is null) { if (element.datum == v) { static if (!ordered) { /* * Move-to-front heuristic: * Move an element to the beginning of the list once it is found. * This scheme ensures that the most recently used items are also * the quickest to find again. */ prevElement.next = element.next; element.next = head; head = element; } return element; } prevElement = element; element = element.next; } return null; } /// unittest { LinkedList!int list; scope(exit) list.free(); assert(list.find(42) is null); list.insertBack(13); list.insertBack(42); auto first = list.find(13); assert(first && first.datum == 13); auto second = list.find(42); assert(second && second.datum == 42); assert(list.find(0) is null); } /** * Convert to array. */ T[] toArray() { T[] arr = New!(T[])(length); foreach(i, v; this) arr[i] = v; return arr; } /// unittest { LinkedList!int list; scope(exit) list.free(); list.insertBack(1); list.insertBack(2); list.insertBack(3); auto arr = list.toArray(); assert(arr == [1,2,3]); Delete(arr); } auto byElement() { struct ByElement { private: LinkedListElement!(T)* _first; public: @property bool empty() { return _first is null; } @property T front() { return _first.datum; } void popFront() { _first = _first.next; } auto save() { return this; } } return ByElement(head); } /// unittest { LinkedList!int list; scope(exit) list.free(); assert(list.byElement().empty); list.insertBack(1); list.insertBack(2); list.insertBack(3); auto range = list.byElement(); import std.range: isInputRange; import std.algorithm: equal; static assert(isInputRange!(typeof(range))); assert(equal(range, [1, 2, 3])); range = list.byElement(); auto saved = range.save(); range.popFront(); assert(equal(range, [2, 3])); assert(equal(saved, [1, 2, 3])); } // For backward compatibility alias append = insertBack; alias insertBeginning = insertFront; alias removeBeginning = removeFront; alias search = find; } ================================================ FILE: dlib/container/mappedlist.d ================================================ /* Copyright (c) 2018-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * A list with string-mapped indices. * * Copyright: Timur Gafarov 2018-2025. * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.container.mappedlist; import dlib.core.ownership; import dlib.core.memory; import dlib.container.array; import dlib.container.dict; /** * A list with string-mapped indices. */ class MappedList(T): Owner { Array!T data; Dict!(size_t, string) indices; this(Owner owner) { super(owner); indices = New!(Dict!(size_t, string))(); } void set(string name, T val) { data.append(val); indices[name] = data.length - 1; } T get(string name) { return data[indices[name]]; } ~this() { data.free(); Delete(indices); } } ================================================ FILE: dlib/container/package.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Containers * * Description: * This package implements generic GC-free data containers, such as linked list, * dynamic array, dictionary, etc. They are based on dlib.core.memory allocators. * dlib.container is useful when writing applications with manual memory management. * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.container; public { import dlib.container.array; import dlib.container.buffer; import dlib.container.bst; import dlib.container.dict; import dlib.container.linkedlist; import dlib.container.mappedlist; import dlib.container.queue; import dlib.container.stack; import dlib.container.spscqueue; } ================================================ FILE: dlib/container/queue.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Queue (based on linked list) * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov, Andrey Penechko, Roman Chistokhodov */ module dlib.container.queue; import dlib.container.array; /** * Queue implementation based on Array. */ struct Queue(T) { private: Array!T array; public: /** * Check if stack has no elements. */ @property bool empty() { return array.length == 0; } /** * Add element to queue. */ void enqueue(const(T) v) { array.insertBack(v); } /** * Remove element from queue. * Returns: Removed element. * Throws: Exception if queue is empty. */ T dequeue() { if (empty) throw new Exception("Queue!(T): queue is empty"); T res = array[0]; array.removeFront(1); return res; } /** * Non-throwing version of dequeue. * Returns: true on success, false on failure. * Element is stored in value. */ bool dequeue(ref T value) nothrow { if (empty) return false; value = array[0]; array.removeFront(1); return true; } /** * Free memory allocated by Queue. */ void free() { array.free(); } } /// unittest { import std.exception: assertThrown; Queue!int q; assertThrown(q.dequeue()); assert(q.empty); q.enqueue(50); q.enqueue(30); q.enqueue(900); int v; q.dequeue(v); assert(v == 50); assert(q.dequeue() == 30); assert(q.dequeue() == 900); assert(q.empty); q.free(); } ================================================ FILE: dlib/container/spscqueue.d ================================================ /* Copyright (c) 2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Wait-free single-producer single-consumer queue. * * Copyright: Timur Gafarov 2025 * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.container.spscqueue; import core.atomic; import dlib.core.memory; import dlib.core.ownership; import dlib.core.thread; import dlib.container.array; /** * Generic, wait-free single-producer single-consumer queue. * * Params: * T = element type. * capacity = Maximum number of elements in the queue. */ struct SPSCQueue(T, size_t capacity) { /// Circular buffer. T[capacity] buffer; /// Producer write index. shared size_t head = 0; /// Consumer read index. shared size_t tail = 0; /// Add an element to the queue. Returns false if full. bool push(T value) { auto next = (head + 1) % capacity; if (next == tail) // queue full return false; buffer[head] = value; atomicStore!(MemoryOrder.rel)(head, next); return true; } /// Remove an element from the queue. Returns false if empty. bool pop(out T value) { if (tail == atomicLoad!(MemoryOrder.acq)(head)) return false; // queue empty value = buffer[tail]; tail = (tail + 1) % capacity; return true; } } ================================================ FILE: dlib/container/stack.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Stack (based on Array) * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov, Andrey Penechko, Roman Chistokhodov */ module dlib.container.stack; import dlib.container.array; /** * Stack implementation based on Array. */ struct Stack(T) { private Array!T array; public: /** * Push element to stack. */ void push(T v) { array.insertBack(v); } /** * Pop top element out. * Returns: Removed element. * Throws: Exception on underflow. */ T pop() { if (empty) throw new Exception("Stack!(T): underflow"); T res = array[$-1]; array.removeBack(1); return res; } /** * Non-throwing version of pop. * Returns: true on success, false on failure. * Element is stored in value. */ bool pop(ref T value) { if (empty) return false; value = array[$-1]; array.removeBack(1); return true; } /** * Top stack element. * Note: Stack must be non-empty. */ T top() { return array[$-1]; } /** * Pointer to top stack element. * Note: Stack must be non-empty. */ T* topPtr() { return &array.data[$-1]; } /** * Check if stack has no elements. */ @property bool empty() nothrow { return array.length == 0; } /** * Free memory allocated by Stack. */ void free() { array.free(); } } /// unittest { import std.exception: assertThrown; Stack!int s; assertThrown(s.pop()); s.push(100); s.push(3); s.push(76); assert(s.top() == 76); int v; s.pop(v); assert(v == 76); assert(s.pop() == 3); assert(s.pop() == 100); assert(s.empty); s.free(); } ================================================ FILE: dlib/core/bitio.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Bit-level manipulations * * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.core.bitio; /** * Endianness */ enum Endian { Little, Big } /** * Returns high 4 bits of a byte */ T hiNibble(T)(T b) { return ((b >> 4) & 0x0F); } /// unittest { assert(hiNibble(0xFE) == 0x0F); } /** * Returns low 4 bits of a byte */ T loNibble(T)(T b) { return (b & 0x0F); } /// unittest { assert(loNibble(0xFE) == 0x0E); } /** * Sets bit at position pos in integer b to state */ T setBit(T)(T b, uint pos, bool state) { if (state) return cast(T)(b | (1 << pos)); else return cast(T)(b & ~(1 << pos)); } /// unittest { assert(setBit(0, 0, true) == 1); assert(setBit(1, 0, false) == 0); } /** * Returns bit at position pos in integer b */ bool getBit(T)(T b, uint pos) { return ((b & (1 << pos)) != 0); } /// unittest { assert(getBit(1, 0) == 1); } ================================================ FILE: dlib/core/compound.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Compile-time sequence (aka tuple) + struct hybrid * * Description: * This template can be used to construct data types on the fly * and return them from functions, which cannot be done with pure compile-time sequence. * One possible use case for such types is returning result and error message * from function instead of throwing an exception. * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 htpps://boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.core.compound; /** * A struct that consists of a compile-time sequence T. Allows square bracket access to the members of a sequence */ struct Compound(T...) { T tuple; alias tuple this; } /** * Returns a Compound consisting of args */ Compound!(T) compound(T...)(T args) { return Compound!(T)(args); } /// unittest { auto c = compound(true, 0.5f, "hello"); assert(c[0] == true); assert(c[1] == 0.5f); assert(c[2] == "hello"); } ================================================ FILE: dlib/core/memory.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Tools for manual memory management * * New/Delete for classes, structs and arrays. It utilizes dlib.memory * for actual memory allocation, so it is possible to switch allocator that is * being used. By default, dlib.memory.mallocator.Mallocator is used. * * Module includes a simple memory profiler that can be turned on with enableMemoryProfiler * function. If active, it will store information about every allocation (type and size), * and will mark those which are leaked (haven't been deleted). * * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.core.memory; import std.stdio; import std.conv; import std.traits; import std.datetime; import std.algorithm; import core.stdc.stdlib; import core.exception: onOutOfMemoryError; import dlib.memory; private __gshared ulong _allocatedMemory = 0; /** * Returns current amount of allocated memory in bytes. This is 0 at program start */ ulong allocatedMemory() { return _allocatedMemory; } private __gshared Mallocator _defaultGlobalAllocator; private __gshared Allocator _globalAllocator; /** * Returns current global Allocator that is used by New and Delete */ Allocator globalAllocator() { if (_globalAllocator is null) { if (_defaultGlobalAllocator is null) _defaultGlobalAllocator = Mallocator.instance; _globalAllocator = _defaultGlobalAllocator; } return _globalAllocator; } /** * Sets global Allocator that is used by New and Delete */ void globalAllocator(Allocator a) { _globalAllocator = a; } struct AllocationRecord { string type; size_t size; string file; int line; ulong id; bool deleted; } private { __gshared bool memoryProfilerEnabled = false; __gshared AllocationRecord[ulong] records; __gshared ulong counter = 0; void addRecord(void* p, string type, size_t size, string file = "", int line = 0) { records[cast(ulong)p] = AllocationRecord(type, size, file, line, counter, false); counter++; } void markDeleted(void* p) { ulong k = cast(ulong)p - psize; records[k].deleted = true; } } /** * Enables or disables memory profiler */ void enableMemoryProfiler(bool mode) { memoryProfilerEnabled = mode; } /** * Prints full allocation list if memory profiler is enabled, otherwise does nothing */ void printMemoryLog() { writeln("----------------------------------------------------"); writeln(" Memory allocation log "); writeln("----------------------------------------------------"); auto keys = records.keys; sort!((a, b) => records[a].id < records[b].id)(keys); foreach(k; keys) { AllocationRecord r = records[k]; if (r.deleted) writefln(" %s - %s byte(s) in %s(%s)", r.type, r.size, r.file, r.line); else writefln("REMAINS: %s - %s byte(s) in %s(%s)", r.type, r.size, r.file, r.line); } writeln("----------------------------------------------------"); writefln("Total amount of allocated memory: %s byte(s)", _allocatedMemory); writeln("----------------------------------------------------"); } /** * Prints leaked allocations if memory profiler is enabled, otherwise does nothing */ void printMemoryLeaks() { writeln("----------------------------------------------------"); writeln(" Memory leaks "); writeln("----------------------------------------------------"); auto keys = records.keys; sort!((a, b) => records[a].id < records[b].id)(keys); foreach(k; keys) { AllocationRecord r = records[k]; if (!r.deleted) writefln("%s - %s byte(s) in %s(%s)", r.type, r.size, r.file, r.line); } writeln("----------------------------------------------------"); writefln("Total amount of leaked memory: %s byte(s)", _allocatedMemory); writeln("----------------------------------------------------"); } interface Freeable { void free(); } enum psize = 8; static if (__VERSION__ >= 2079) { T allocate(T, A...) (A args, string file = __FILE__, int line = __LINE__) if (is(T == class)) { enum size = __traits(classInstanceSize, T); void* p = globalAllocator.allocate(size+psize).ptr; if (!p) onOutOfMemoryError(); auto memory = p[psize..psize+size]; *cast(size_t*)p = size; _allocatedMemory += size; if (memoryProfilerEnabled) { addRecord(p, T.stringof, size, file, line); } auto res = emplace!(T, A)(memory, args); return res; } T* allocate(T, A...) (A args, string file = __FILE__, int line = __LINE__) if (is(T == struct)) { enum size = T.sizeof; void* p = globalAllocator.allocate(size+psize).ptr; if (!p) onOutOfMemoryError(); auto memory = p[psize..psize+size]; *cast(size_t*)p = size; _allocatedMemory += size; if (memoryProfilerEnabled) { addRecord(p, T.stringof, size, file, line); } return emplace!(T, A)(memory, args); } T allocate(T) (size_t length, string file = __FILE__, int line = __LINE__) if (isArray!T) { alias AT = ForeachType!T; size_t size = length * AT.sizeof; auto mem = globalAllocator.allocate(size+psize).ptr; if (!mem) onOutOfMemoryError(); T arr = cast(T)mem[psize..psize+size]; foreach(ref v; arr) v = v.init; *cast(size_t*)mem = size; _allocatedMemory += size; if (memoryProfilerEnabled) { addRecord(mem, T.stringof, size, file, line); } return arr; } } else { T allocate(T, A...) (A args) if (is(T == class)) { enum size = __traits(classInstanceSize, T); void* p = globalAllocator.allocate(size+psize).ptr; if (!p) onOutOfMemoryError(); auto memory = p[psize..psize+size]; *cast(size_t*)p = size; _allocatedMemory += size; if (memoryProfilerEnabled) { addRecord(p, T.stringof, size); } auto res = emplace!(T, A)(memory, args); return res; } T* allocate(T, A...) (A args) if (is(T == struct)) { enum size = T.sizeof; void* p = globalAllocator.allocate(size+psize).ptr; if (!p) onOutOfMemoryError(); auto memory = p[psize..psize+size]; *cast(size_t*)p = size; _allocatedMemory += size; if (memoryProfilerEnabled) { addRecord(p, T.stringof, size); } return emplace!(T, A)(memory, args); } T allocate(T) (size_t length) if (isArray!T) { alias AT = ForeachType!T; size_t size = length * AT.sizeof; auto mem = globalAllocator.allocate(size+psize).ptr; if (!mem) onOutOfMemoryError(); T arr = cast(T)mem[psize..psize+size]; foreach(ref v; arr) v = v.init; *cast(size_t*)mem = size; _allocatedMemory += size; if (memoryProfilerEnabled) { addRecord(mem, T.stringof, size); } return arr; } } void deallocate(T)(ref T obj) if (isArray!T) { void* p = cast(void*)obj.ptr; size_t size = *cast(size_t*)(p - psize); globalAllocator.deallocate((p - psize)[0..size+psize]); _allocatedMemory -= size; if (memoryProfilerEnabled) { markDeleted(p); } obj.length = 0; } void deallocate(T)(T obj) if (is(T == class) || is(T == interface)) { Object o = cast(Object)obj; void* p = cast(void*)o; size_t size = *cast(size_t*)(p - psize); destroy(obj); globalAllocator.deallocate((p - psize)[0..size+psize]); _allocatedMemory -= size; if (memoryProfilerEnabled) { markDeleted(p); } } void deallocate(T)(T* obj) { void* p = cast(void*)obj; size_t size = *cast(size_t*)(p - psize); destroy(obj); globalAllocator.deallocate((p - psize)[0..size+psize]); _allocatedMemory -= size; if (memoryProfilerEnabled) { markDeleted(p); } } /** Creates an object of type T and calls its constructor if necessary. Description: This is an equivalent for D's new opetator. It allocates arrays, classes and structs on a heap using currently set globalAllocator. Arguments to this function are passed to constructor. Examples: ---- MyClass c = New!MyClass(10, 4, 5); int[] arr = New!(int[])(100); assert(arr.length == 100); MyStruct* s = New!MyStruct; Delete(c); Delete(arr); Delete(s); ---- */ alias New = allocate; /** Destroys an object of type T previously created by New and calls its destructor if necessary. Examples: ---- MyClass c = New!MyClass(10, 4, 5); int[] arr = New!(int[])(100); assert(arr.length == 100); MyStruct* s = New!MyStruct; Delete(c); Delete(arr); Delete(s); ---- */ alias Delete = deallocate; unittest { auto mem = allocatedMemory(); int[] arr = New!(int[])(100); assert(arr.length == 100); assert(allocatedMemory() - mem == uint.sizeof * 100); Delete(arr); assert(arr.length == 0); struct Foo { int a; } Foo* foo = New!Foo(10); assert(foo.a == 10); Delete(foo); } ================================================ FILE: dlib/core/mutex.d ================================================ /* Copyright (c) 2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Cross-platform thread synchronization primitive * * Copyright: Timur Gafarov 2025. * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.core.mutex; version(Windows) { import core.sys.windows.winbase; } else version(Posix) { import core.sys.posix.pthread; } /** * Mutex structure. * Uses CRITICAL_SECTION under Windows, pthread_mutex_t under Posix */ struct Mutex { version(Windows) { CRITICAL_SECTION _mutex; } else version(Posix) { pthread_mutex_t _mutex; } /// Initialize the mutex int init() { version(Windows) { InitializeCriticalSection(&_mutex); return 0; } else version(Posix) { return pthread_mutex_init(&_mutex, null); } else return 0; } /// Enter critical section. If the mutex is already locked, block the thread until it becomes available int lock() { version(Windows) { EnterCriticalSection(&_mutex); return 0; } else version(Posix) { return pthread_mutex_lock(&_mutex); } else return 0; } /// Try to enter critical section. Return immediately if the mutex is already locked int tryLock() { version(Windows) { return !TryEnterCriticalSection(&_mutex); } else version(Posix) { return pthread_mutex_trylock(&_mutex); } else return 0; } /// Leave critical section int unlock() { version(Windows) { LeaveCriticalSection(&_mutex); return 0; } else version(Posix) { return pthread_mutex_unlock(&_mutex); } else return 0; } /// Destroy the mutex int destroy() { version(Windows) { DeleteCriticalSection(&_mutex); return 0; } else version(Posix) { return pthread_mutex_destroy(&_mutex); } else return 0; } } /// unittest { import dlib.core.memory; import dlib.core.thread; int x = 0; Mutex mutex; mutex.init(); void add() { mutex.lock(); int local = x; local = local + 1; x = local; mutex.unlock(); } void sub() { mutex.lock(); int local = x; local = local - 1; x = local; mutex.unlock(); } Thread[100] threads; foreach(i; 0..threads.length/2) { threads[i] = New!Thread(&add); threads[i].start(); } foreach(i; threads.length/2..threads.length) { threads[i] = New!Thread(&sub); threads[i].start(); } foreach(t; threads) { t.join(); } assert(x == 0); } ================================================ FILE: dlib/core/oop.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Prototype-based OOP system for structs. * * Description: * Supports multiple inheritance, parametric polymorphism (struct interfaces), * interface inheritance * * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.core.oop; import std.traits; /** * Inheritance mixin * * Description: * Inserts structs specified by PT type tuple as members and a dispatcher method * that statically transfers any method calls and member accesses to corresponding * child struct. */ mixin template Inherit(PT...) { PT _parent; alias _parentTypeTuple = PT; alias _parent this; template opDispatch(string s) { enum e = tupleElemWithMember!(s, PT); static if (hasMethod!(typeof(_parent[e]), s)) { @property auto ref opDispatch(T...)(T params) { return __traits(getMember, _parent[e], s)(params); } } else { @property auto ref opDispatch() { return __traits(getMember, _parent[e], s); } @property auto ref opDispatch(T)(T val) { return __traits(getMember, _parent[e], s) = val; } } } } /// unittest { struct Foo { int x = 100; int foo() { return 11; } } struct Bar { int y = 99; int bar() { return 22; } } struct TestObj { mixin Inherit!(Foo, Bar); } TestObj obj; obj.x *= 2; obj.y = 10; assert(obj.x == 200); assert(obj.y == 10); assert(obj.foo() == 11); assert(obj.bar() == 22); } /** * Returns index of a tuple element which has specified member (s) */ size_t tupleElemWithMember(string s, T...)() { foreach(i, type; T) { static if (__traits(hasMember, type, s)) return i; } assert(0); } /** * Test if type (T) has specified method (m), local or derived */ bool hasMethod(T, string m)() { static if (__traits(hasMember, T, m)) { static if (isMethod!(__traits(getMember, T, m))) { return true; } } static if (__traits(hasMember, T, "_parentTypeTuple")) { foreach(i, parentType; T._parentTypeTuple) { static if (hasMethod!(parentType, m)) { return true; } } return false; } else { return false; } } /// unittest { struct Foo { int bar() { return 0; } } assert(hasMethod!(Foo, "bar")() == true); assert(hasMethod!(Foo, "foo")() == false); } /** * Test if F is a method */ template isMethod(alias F) { enum isMethod = isSomeFunction!F && !isFunctionPointer!F && !isDelegate!F; } ================================================ FILE: dlib/core/ownership.d ================================================ /* Copyright (c) 2017-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Class-based object ownership system * * Description: * Object ownership system similar to Delphi's. All classes deriving from Owner * can store references to objects implementing Owned interface (and other Owner * objects as well). When an owner is deleted, its owned objects are also deleted. * * This module is not compatible with GC-collected objects. It can be used only with * dlib.core.memory. Using it with objects allocated any other way will cause application to crash. * * Copyright: Timur Gafarov 2017-2025. * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.core.ownership; import dlib.core.memory; import dlib.container.array; /** * Interface for objects that can be owned, but not own other objects */ interface Owned { } /** * Basic owner object class. * When you delete it, all owned object are automatically deleted */ class Owner: Owned { protected Array!Owned ownedObjects; /** * Constructor. owner can be null, in this case object won't have an owner. * Such objects are called root owners and should be deleted manually. */ this(Owner owner) { if (owner) owner.addOwnedObject(this); } /// Add owned object. Usually you don't have to do it explicitly, just pass the owner to constructor void addOwnedObject(Owned obj) { ownedObjects.append(obj); } /// Delete owned object without deleting object itself void clearOwnedObjects() { foreach(i, obj; ownedObjects) Delete(obj); ownedObjects.free(); } /// Delete particular owned object, if it is there void deleteOwnedObject(Owned obj) { if (ownedObjects.removeFirst(obj)) { Delete(obj); } } /// Destructor ~this() { clearOwnedObjects(); } } /// unittest { class Test: Owner { string name; static bool[string] available; this(string name, Owner o = null) { super(o); this.name = name; available[name] = true; } ~this() { available[name] = false; } } Test obj1 = New!Test("obj1", null); Test obj2 = New!Test("obj2", obj1); Test obj3 = New!Test("obj3", obj2); obj2.deleteOwnedObject(obj3); assert(!Test.available["obj3"]); Delete(obj1); assert(!Test.available["obj2"]); assert(!Test.available["obj1"]); } ================================================ FILE: dlib/core/package.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Provides functionality that is used by all other dlib packages * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.core; public { import dlib.core.bitio; import dlib.core.compound; import dlib.core.memory; import dlib.core.mutex; import dlib.core.oop; import dlib.core.ownership; import dlib.core.stream; import dlib.core.tuple; import dlib.core.thread; } ================================================ FILE: dlib/core/stream.d ================================================ /* Copyright (c) 2014-2025 Martin Cejp Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Binary I/O stream interfaces * * Copyright: Martin Cejp 2014-2025. * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Martin Cejp, Timur Gafarov */ module dlib.core.stream; import std.bitmanip; import std.stdint; import std.conv; import dlib.core.memory; alias StreamPos = uint64_t; alias StreamSize = uint64_t; alias StreamOffset = int64_t; /// An exception which is throwed on stream errors class SeekException : Exception { this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { super(msg, file, line, next); } } /** * Seekable stream interface * * Description: * Represents a stream container that knows the size of a stream * and allows to change byte position within the stream. */ interface Seekable { /// Returns current position StreamPos getPosition() @property; /** * Attempts to set current position to pos. * Returns true on success, false on failure */ bool setPosition(StreamPos pos); /// Returns the size of a stream in bytes StreamSize size(); /** * Attempts to set current position to pos. * Throws SeekException on failure */ final StreamPos position(StreamPos pos) { if (!setPosition(pos)) throw new SeekException("Cannot set Seekable position to " ~ pos.to!string); return pos; } /// ditto final StreamPos position() { return getPosition(); } /** * Relatively changes position. * amount defines an offset from the current position (can be negative). * Throws SeekException on failure */ final StreamPos seek(StreamOffset amount) { immutable StreamPos seekTo = getPosition() + amount; if (!setPosition(seekTo)) throw new SeekException("Cannot set Seekable position to " ~ seekTo.to!string); return seekTo; } } /** * A parent interface for all stream types */ interface Stream: Seekable { /// Closes the stream. Closed stream cannot be read or written any more void close(); /// Returns true if it is legal to use Seekable functionality on this stream bool seekable(); } /** * A stream inteface that allows to read data from it. * Reading any data implies position advance by corresponding number of bytes. * Methods shouldn't throw on EOF, may throw on a more serious error */ interface InputStream: Stream { /// Returns true if there are any data to read. false means end of the stream. bool readable(); /** * Attempts to read count bytes from stream and stores them in memory * pointing by buffer. Returns number of bytes actually read */ size_t readBytes(void* buffer, size_t count); /** * Attempts to fill an array with raw data from stream. * Returns true if the array was filled, false otherwise */ final bool fillArray(T)(T[] array) { immutable size_t len = array.length * T.sizeof; return readBytes(array.ptr, len) == len; } /** * Reads little-endian integer, converts to native-endian * and stores in value */ final bool readLE(T)(T* value) { ubyte[T.sizeof] buffer; if (readBytes(buffer.ptr, buffer.length) != buffer.length) return false; *value = littleEndianToNative!T(buffer); return true; } /** * Reads big-endian integer, converts to native-endian * and stores in value */ final bool readBE(T)(T* value) { ubyte[T.sizeof] buffer; if (readBytes(buffer.ptr, buffer.length) != buffer.length) return false; *value = bigEndianToNative!T(buffer); return true; } } /** * A stream interface that allows to write data into it. * Methods shouldn't throw on full disk, may throw on a more serious error */ interface OutputStream: Stream { /// Implementation-specific method. Usually it writes any unwritten data from output buffer void flush(); /// Returns true if stream can be written to, false otherwise bool writeable(); /** * Attempts to write count bytes from the memory pointed by buffer. * Returns number of bytes actually written */ size_t writeBytes(const void* buffer, size_t count); /** * Attempts to write an array. * Returns true if all elements were written, false otherwise */ final bool writeArray(T)(const T[] array) { immutable size_t len = array.length * T.sizeof; return writeBytes(array.ptr, len) == len; } /** * Attempts to write a string as zero-terminated. * Returns true if entire string was written, false otherwise */ final bool writeStringz(string text) { ubyte[1] zero = [0]; return writeBytes(text.ptr, text.length) && writeBytes(zero.ptr, zero.length); } /// Writes an integer in little-endian encoding final bool writeLE(T)(const T value) { ubyte[T.sizeof] buffer = nativeToLittleEndian!T(value); return writeBytes(buffer.ptr, buffer.length) == buffer.length; } /// Writes an integer in big-endian encoding final bool writeBE(T)(const T value) { ubyte[T.sizeof] buffer = nativeToBigEndian!T(value); return writeBytes(buffer.ptr, buffer.length) == buffer.length; } } /** * A stream that allows both reading and writing data */ interface IOStream: InputStream, OutputStream { } /** * While input is readable, reads data from input and writes it to output. * Returns number of bytes read */ StreamSize copyFromTo(InputStream input, OutputStream output) { ubyte[0x1000] buffer; StreamSize total = 0; while (input.readable) { size_t have = input.readBytes(buffer.ptr, buffer.length); if (have == 0) break; output.writeBytes(buffer.ptr, have); total += have; } return total; } /** * An InputStream that encapsulates contents of an array */ class ArrayStream: InputStream { import std.algorithm; /// Constructor. Initializes stream as empty this() { } /** * Constructor. Initializes stream with an array of bytes. * size delimits maximum read size */ this(ubyte[] data, size_t size) { assert(size_ <= data.length); this.size_ = size; this.data = data; } /** * Constructor. Initializes stream with an array of bytes */ this(ubyte[] data) { this(data, data.length); } override void close() { this.pos = 0; this.size_ = 0; this.data = null; } override bool readable() { return pos < size_; } override size_t readBytes(void* buffer, size_t count) { import core.stdc.string; count = min(count, size_ - pos); // whoops, memcpy out of nowhere, can we do better than that? memcpy(buffer, data.ptr + pos, count); pos += count; return count; } override bool seekable() { return true; } override StreamPos getPosition() { return pos; } override bool setPosition(StreamPos pos) { if (pos > size_) return false; this.pos = cast(size_t)pos; return true; } override StreamSize size() { return size_; } private: size_t pos = 0, size_ = 0; ubyte[] data; // data.length is capacity } /// unittest { ubyte[] arr = [23, 13, 42, 71, 0, 1, 1, 2, 3, 5, 8]; auto stream = new ArrayStream(arr); assert(stream.size() == arr.length); ubyte[4] buf; assert(stream.readBytes(buf.ptr, buf.length) == buf.length); assert(buf == [23, 13, 42, 71]); assert(stream.getPosition() == buf.length); assert(stream.setPosition(6)); assert(stream.getPosition == 6); assert(stream.readBytes(buf.ptr, buf.length) == buf.length); assert(buf == [1, 2, 3, 5]); assert(stream.readBytes(buf.ptr, buf.length) == 1); assert(buf[0] == 8); assert(!stream.readable); stream.setPosition(1); assert(stream.readable); stream.seek(4); assert(stream.readBytes(buf.ptr, buf.length) == buf.length); assert(buf == [1, 1, 2, 3]); assert(stream.setPosition(arr.length)); assert(!stream.setPosition(arr.length + 1)); stream.close(); assert(!stream.readable); } /** * An input range that reads data from InputStream by fixed chunks, * storing them in user-provided array */ struct BufferedStreamReader { InputStream stream; ubyte[] buffer; ubyte[] front; /// Constructor. Initializes range with a stream and a buffer this(InputStream istrm, ubyte[] buffer) { stream = istrm; this.buffer = buffer; popFront(); } bool empty = false; void popFront() { empty = !stream.readable(); size_t readLen = stream.readBytes(buffer.ptr, buffer.length); if (readLen > 0) front = buffer[0..readLen]; } } ================================================ FILE: dlib/core/thread.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Cross-platform thread class. Supports Windows and Posix * * Description: * This module provides a platform-independent multithreading API. * For now, it supports Windows and Posix threads (pthreads). The interface * is greatly inspired by core.thread from Phobos, but dlib.core.thread * is fully GC-free and can be used with dlib.core.memory allocators. * * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.core.thread; version(Windows) { extern(Windows): alias HANDLE = void*; struct SECURITY_ATTRIBUTES { uint nLength; void* lpSecurityDescriptor; int bInheritHandle; } alias LPTHREAD_START_ROUTINE = extern(Windows) uint function(const(void)*); void* CreateThread( const(SECURITY_ATTRIBUTES)* lpThreadAttributes, size_t dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, void* lpParameter, uint dwCreationFlags, uint* lpThreadId ); uint WaitForMultipleObjects( uint nCount, const(HANDLE)* lpHandles, int bWaitAll, uint dwMilliseconds ); int TerminateThread( const(void)* hThread, uint dwExitCode ); int GetExitCodeThread( const(void)* hThread, uint* lpExitCode ); int CloseHandle( const(void)* hObject ); enum INFINITE = uint.max; enum STILL_ACTIVE = 259; } version(Posix) { import core.sys.posix.pthread; } version(Windows) { private extern(Windows) void Sleep(uint msec); } else version(Posix) { import core.stdc.time; import core.sys.posix.time; import core.sys.posix.signal; } /** * Base class for creating threads */ class Thread { private void function() func; private void delegate() dlgt; private bool callFunc; private bool initialized = false; version(Windows) { private void* winThread; } version(Posix) { private pthread_t posixThread; private bool running = false; } /// Constructor. Initializes Thread using a function pointer this(void function() func) { this.func = func; callFunc = true; } /// Constructor. Initializes Thread using a delegate this(void delegate() dlgt) { this.dlgt = dlgt; callFunc = false; } /// Destructor ~this() { version(Windows) { if (winThread) CloseHandle(winThread); } version(Posix) { if (initialized) pthread_detach(posixThread); } } /// Starts the thread void start() { version(Windows) { if (winThread) CloseHandle(winThread); uint threadId; winThread = CreateThread(null, cast(size_t)0, &winThreadFunc, cast(void*)this, cast(uint)0, &threadId); assert(winThread !is null); initialized = true; } version(Posix) { running = true; int error = pthread_create(&posixThread, null, &posixThreadFunc, cast(void*)this); assert(error == 0); initialized = true; } } /// Waits for the thread to terminate void join() { version(Windows) { WaitForMultipleObjects(1u, &winThread, 1, INFINITE); } version(Posix) { pthread_join(posixThread, null); } } /// Checks if thread is running bool isRunning() { version(Windows) { uint c = 0; GetExitCodeThread(winThread, &c); return (c == STILL_ACTIVE); } version(Posix) { return running; } } /// Stops the thread immediately. This functionality is unsafe, use with care void terminate() { version(Windows) { TerminateThread(winThread, 1); } version(Posix) { pthread_cancel(posixThread); running = false; } } version(Windows) { extern(Windows) static uint winThreadFunc(const(void)* lpParam) { Thread t = cast(Thread)lpParam; if (t.callFunc) t.func(); else t.dlgt(); return 0; } } version(Posix) { extern(C) static void* posixThreadFunc(void* arg) { Thread t = cast(Thread)arg; if (t.callFunc) t.func(); else t.dlgt(); t.running = false; return null; } } /// Wait for specified amout of milliseconds static void sleep(uint msec) { version(Windows) { Sleep(msec); } else version(Posix) { timespec ts; ts.tv_sec = msec / 1000; ts.tv_nsec = (msec % 1000) * 1000000; nanosleep(&ts, null); } } /// unittest { Thread.sleep(100); assert(true); } } ================================================ FILE: dlib/core/tuple.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Templates that construct tuples of numeric values * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.core.tuple; /// Create a tuple template Tuple(E...) { alias Tuple = E; } /// Yields a tuple of integer literals from 0 to stop template RangeTuple(int stop) { static if (stop <= 0) alias RangeTuple = Tuple!(); else alias RangeTuple = Tuple!(RangeTuple!(stop-1), stop-1); } /// Yields a tuple of integer literals from start to stop template RangeTuple(int start, int stop) { static if (stop <= start) alias RangeTuple = Tuple!(); else alias RangeTuple = Tuple!(RangeTuple!(start, stop-1), stop-1); } /// Yields a tuple of integer literals from start to stop with defined step template RangeTuple(int start, int stop, int step) { static assert(step != 0, "RangeTuple: step must be != 0"); static if (step > 0) { static if (stop <= start) alias RangeTuple = Tuple!(); else alias RangeTuple = Tuple!(RangeTuple!(start, stop-step, step), stop-step); } else { static if (stop >= start) alias RangeTuple = Tuple!(); else alias RangeTuple = Tuple!(RangeTuple!(start, stop-step, step), stop-step); } } ================================================ FILE: dlib/filesystem/delegaterange.d ================================================ /* Copyright (c) 2014-2025 Martin Cejp Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Martin Cejp 2014-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Martin Cejp */ module dlib.filesystem.delegaterange; private import std.range; /** * An input range that enumerates items by obtaining them from a delegate until it returns 0 */ class DelegateInputRange(T): InputRange!T { bool delegate(out T t) fetch; bool have = false; T front_; this(bool delegate(out T t) fetch) { this.fetch = fetch; } override bool empty() { if (!have) have = fetch(front_); return !have; } override T front() { return front_; } override void popFront() { have = false; } override T moveFront() { have = false; return front_; } override int opApply(scope int delegate(T) dg) { int result = 0; for (size_t i = 0; !empty; i++) { result = dg(moveFront); if (result != 0) break; } return result; } override int opApply(scope int delegate(size_t, T) dg) { int result = 0; for (size_t i = 0; !empty; i++) { result = dg(i, moveFront); if (result != 0) break; } return result; } } ================================================ FILE: dlib/filesystem/dirrange.d ================================================ /* Copyright (c) 2014-2025 Martin Cejp, Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Martin Cejp, Timur Gafarov 2014-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Martin Cejp, Timur Gafarov */ module dlib.filesystem.dirrange; import dlib.filesystem.delegaterange; import dlib.filesystem.filesystem; /// A DelegateInputRange of DirEntry values alias DirRange = DelegateInputRange!DirEntry; ================================================ FILE: dlib/filesystem/filesystem.d ================================================ /* Copyright (c) 2014-2025 Martin Cejp, Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Martin Cejp, Timur Gafarov 2014-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Martin Cejp, Timur Gafarov */ module dlib.filesystem.filesystem; import std.datetime; import std.range; import std.regex; import std.algorithm; import std.string; import dlib.core.stream; import dlib.filesystem.dirrange; /// File size type alias FileSize = StreamSize; /// If a file is readable, FileStat.permissions will have PRead bits set enum PRead = 0x01; /// If a file is writable, FileStat.permissions will have PWrite bits set enum PWrite = 0x02; /// If a file is executable, FileStat.permissions will have PExecute bits set enum PExecute = 0x04; /// Holds general information about a file or directory. struct FileStat { /// True if a file is not a directory bool isFile; /// True if a file is a directory bool isDirectory; /// File size. Valid only if isFile is true FileSize sizeInBytes; /// File creation date/time SysTime creationTimestamp; /// File modification date/time SysTime modificationTimestamp; /// Permissions of a file in a given filesystem. Bit combination of PRead, PWrite, PExecute int permissions; } /// A filesystem entry - file or directory struct DirEntry { /// Entry base name (relative to its containing directory) string name; /// True if an entry is a file bool isFile; /// True if an entry is a directory bool isDirectory; } /// A directory in the file system. interface Directory { /// void close(); /// Get directory contents as a range. /// This range $(I should) be lazily evaluated when practical. /// The entries "." and ".." are skipped. InputRange!DirEntry contents(); } /// A filesystem limited to read access. interface ReadOnlyFileSystem { /** Get file or directory stats. Example: --- void printFileInfo(ReadOnlyFileSystem fs, string filename) { FileStat stat; writef("'%s'\t", filename); if (!fs.stat(filename, stat)) { writeln("ERROR"); return; } if (stat.isFile) writefln("%u", stat.sizeInBytes); else if (stat.isDirectory) writeln("DIR"); writefln(" created: %s", to!string(stat.creationTimestamp)); writefln(" modified: %s", to!string(stat.modificationTimestamp)); } --- */ bool stat(string filename, out FileStat stat); /** Open a file for input. Returns: a valid InputStream on success, null on failure */ InputStream openForInput(string filename); /** Open a directory. */ Directory openDir(string path); } /// A file system with read/write access. interface FileSystem: ReadOnlyFileSystem { /// File access flags. enum { read = 1, write = 2, } /// File creation flags. enum { create = 1, truncate = 2, } /** Open a file for output. Returns: a valid OutputStream on success, null on failure */ OutputStream openForOutput(string filename, uint creationFlags); /** Open a file for input & output. Returns: a valid IOStream on success, null on failure */ IOStream openForIO(string filename, uint creationFlags); //IOStream openFile(string filename, uint accessFlags, uint creationFlags); /** Create a new directory. Returns: true if a new directory was created Examples: --- fs.createDir("New Directory", false); fs.createDir("nested/directories/are/easy", true); --- */ bool createDir(string path, bool recursive); // BROKEN API. Must define semantics for non-atomic move cases (e.g. moving a file to a different drive) //bool move(string path, string newPath); /** Permanently delete a file or directory. */ bool remove(string path, bool recursive); } /** Find files in the specified directory Params: rofs = filesystem to scan baseDir = path to the base directory (if empty, defaults to current working directory) recursive = if true, the search will recurse into subdirectories Examples: --- void listImagesInDirectory(ReadOnlyFileSystem fs, string baseDir = "") { foreach (entry; fs.findFiles(baseDir, true) .filter!(entry => entry.isFile) .filter!(entry => !matchFirst(entry.name, `.*\.(gif|jpg|png)$`).empty)) { writefln("%s", entry.name); } } --- */ InputRange!DirEntry findFiles(ReadOnlyFileSystem rofs, string baseDir, bool recursive) { // Do some magic so that we don't have to keep our own stack import core.thread; DirEntry entry; // findFiles insists on calling us back (it's recursive), but we can trap it in a Fiber auto search = new Fiber(delegate void() { findFiles(rofs, baseDir, recursive, delegate int(ref DirEntry de) { // save the data (D doesn't allow to yield it directly) // and jump outside of the .call() (see below) entry = de; Fiber.yield(); // after resuming, return to findFiles for another round return 0; }); // state becomes TERM after we're resumed after returning the last entry }); return new DirRange(delegate bool(out DirEntry de) { // terminated before? if (search.state == Fiber.State.TERM) return false; // jumps into our search delegate search.call(); // last entry had been returned last time? // (even findFiles didn't know until we returned to it again) if (search.state == Fiber.State.TERM) return false; de = entry; return true; }); } private int findFiles(ReadOnlyFileSystem rofs, string baseDir, bool recursive, int delegate(ref DirEntry entry) dg) { Directory dir = rofs.openDir(baseDir); if (dir is null) return 0; int result = 0; try { foreach (entry; dir.contents) { if (!baseDir.empty) entry.name = baseDir ~ "/" ~ entry.name; result = dg(entry); if (result != 0) return result; if (recursive && entry.isDirectory) { result = findFiles(rofs, entry.name, recursive, dg); if (result != 0) return result; } } } finally { dir.close(); } return result; } ================================================ FILE: dlib/filesystem/local.d ================================================ /* Copyright (c) 2014-2025 Martin Cejp Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Martin Cejp 2014-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Martin Cejp */ module dlib.filesystem.local; import std.array; import std.conv; import std.datetime; import std.path; import std.range; import std.stdio; import std.string; import dlib.core.stream; import dlib.filesystem.filesystem; import dlib.filesystem.dirrange; version (Posix) { import dlib.filesystem.posix.common; import dlib.filesystem.posix.directory; import dlib.filesystem.posix.file; } else version (Windows) { import dlib.filesystem.windows.common; import dlib.filesystem.windows.directory; import dlib.filesystem.windows.file; } // TODO: Should probably check for FILE_ATTRIBUTE_REPARSE_POINT before recursing /// LocalFileSystem class LocalFileSystem: FileSystem { override InputStream openForInput(string filename) { return cast(InputStream) openFile(filename, read, 0); } override OutputStream openForOutput(string filename, uint creationFlags) { return cast(OutputStream) openFile(filename, write, creationFlags); } override IOStream openForIO(string filename, uint creationFlags) { return openFile(filename, read | write, creationFlags); } override bool createDir(string path, bool recursive) { import std.algorithm; if (recursive) { ptrdiff_t index = max(path.lastIndexOf('/'), path.lastIndexOf('\\')); if (index != -1) createDir(path[0..index], true); } version(Posix) { return mkdir(toStringz(path), access_0755) == 0; } else version (Windows) { return CreateDirectoryW(toUTF16z(path), null) != 0; } else throw new Exception("Not implemented."); } override Directory openDir(string path) { version(Posix) { DIR* d = opendir(!path.empty ? toStringz(path) : "."); if (d == null) return null; else return new PosixDirectory(this, d, !path.empty ? path ~ "/" : ""); } else version(Windows) { string npath = !path.empty ? buildNormalizedPath(path) : "."; DWORD attributes = GetFileAttributesW(toUTF16z(npath)); if (attributes == INVALID_FILE_ATTRIBUTES) return null; if (attributes & FILE_ATTRIBUTE_DIRECTORY) return new WindowsDirectory(this, npath, !path.empty ? path ~ "/" : ""); else return null; } else throw new Exception("Not implemented."); } override bool stat(string path, out FileStat stat_out) { version(Posix) { stat_t st; if (stat_(toStringz(path), &st) != 0) return false; stat_out.isFile = S_ISREG(st.st_mode); stat_out.isDirectory = S_ISDIR(st.st_mode); stat_out.sizeInBytes = st.st_size; stat_out.creationTimestamp = SysTime(unixTimeToStdTime(st.st_ctime)); auto modificationStdTime = unixTimeToStdTime(st.st_mtime); static if (is(typeof(st.st_mtimensec))) { modificationStdTime += st.st_mtimensec / 100; } stat_out.modificationTimestamp = SysTime(modificationStdTime); if ((st.st_mode & S_IRUSR) | (st.st_mode & S_IRGRP) | (st.st_mode & S_IROTH)) stat_out.permissions |= PRead; if ((st.st_mode & S_IWUSR) | (st.st_mode & S_IWGRP) | (st.st_mode & S_IWOTH)) stat_out.permissions |= PWrite; if ((st.st_mode & S_IXUSR) | (st.st_mode & S_IXGRP) | (st.st_mode & S_IXOTH)) stat_out.permissions |= PExecute; return true; } else version(Windows) { WIN32_FILE_ATTRIBUTE_DATA data; auto p = toUTF16z(path); if (!GetFileAttributesExW(p, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &data)) return false; if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) stat_out.isDirectory = true; else stat_out.isFile = true; stat_out.sizeInBytes = (cast(FileSize) data.nFileSizeHigh << 32) | data.nFileSizeLow; stat_out.creationTimestamp = SysTime(FILETIMEToStdTime(&data.ftCreationTime)); stat_out.modificationTimestamp = SysTime(FILETIMEToStdTime(&data.ftLastWriteTime)); stat_out.permissions = 0; PACL pacl; PSECURITY_DESCRIPTOR secDesc; TRUSTEE_W trustee; trustee.pMultipleTrustee = null; trustee.MultipleTrusteeOperation = MULTIPLE_TRUSTEE_OPERATION.NO_MULTIPLE_TRUSTEE; trustee.TrusteeForm = TRUSTEE_FORM.TRUSTEE_IS_NAME; trustee.TrusteeType = TRUSTEE_TYPE.TRUSTEE_IS_UNKNOWN; trustee.ptstrName = cast(wchar*)"CURRENT_USER"w.ptr; GetNamedSecurityInfoW(cast(wchar*)p, SE_OBJECT_TYPE.SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, null, null, &pacl, null, &secDesc); if (pacl) { uint access; GetEffectiveRightsFromAcl(pacl, &trustee, &access); if (access & ACTRL_FILE_READ) stat_out.permissions |= PRead; if ((access & ACTRL_FILE_WRITE) && !(data.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) stat_out.permissions |= PWrite; if (access & ACTRL_FILE_EXECUTE) stat_out.permissions |= PExecute; } return true; } else throw new Exception("Not implemented."); } /* override bool move(string path, string newPath) { // TODO: should we allow newPath to actually be a directory? return rename(toStringz(path), toStringz(newPath)) == 0; } */ override bool remove(string path, bool recursive) { FileStat stat; if (!this.stat(path, stat)) return false; return remove(path, stat.isDirectory, recursive); } private: version(Posix) { enum access_0644 = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; enum access_0755 = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; } IOStream openFile(string filename, uint accessFlags, uint creationFlags) { version(Posix) { int flags; switch (accessFlags & (read | write)) { case read: flags = O_RDONLY; break; case write: flags = O_WRONLY; break; case (read | write): flags = O_RDWR; break; default: flags = 0; break; } if (creationFlags & FileSystem.create) flags |= O_CREAT; if (creationFlags & FileSystem.truncate) flags |= O_TRUNC; int fd = open(toStringz(filename), flags, access_0644); if (fd < 0) return null; else return new PosixFile(fd, accessFlags); } else version(Windows) { DWORD access = 0; if (accessFlags & read) access |= GENERIC_READ; if (accessFlags & write) access |= GENERIC_WRITE; DWORD creationMode; switch (creationFlags & (create | truncate)) { case 0: creationMode = OPEN_EXISTING; break; case create: creationMode = OPEN_ALWAYS; break; case truncate: creationMode = TRUNCATE_EXISTING; break; case (create | truncate): creationMode = CREATE_ALWAYS; break; default: creationMode = OPEN_EXISTING; break; } HANDLE file = CreateFileW(toUTF16z(filename), access, FILE_SHARE_READ, null, creationMode, FILE_ATTRIBUTE_NORMAL, null); if (file == INVALID_HANDLE_VALUE) return null; else return new WindowsFile(file, accessFlags); } else throw new Exception("Not implemented."); } bool remove(string path, bool isDirectory, bool recursive) { if (isDirectory && recursive) { // Remove contents auto dir = openDir(path); try { foreach (entry; dir.contents) remove(path ~ "/" ~ entry.name, entry.isDirectory, recursive); } finally { dir.close(); } } version(Posix) { if (isDirectory) return rmdir(toStringz(path)) == 0; else return std.stdio.remove(toStringz(path)) == 0; } else version(Windows) { if (isDirectory) return RemoveDirectoryW(toUTF16z(path)) != 0; else return DeleteFileW(toUTF16z(path)) != 0; } else throw new Exception("Not implemented."); } } private ReadOnlyFileSystem rofs; private FileSystem fs; static this() { // decouple dependency from the rest of this module import dlib.filesystem.local; setFileSystem(new LocalFileSystem); } void setFileSystem(FileSystem fs_) { rofs = fs_; fs = fs_; } void setFileSystemReadOnly(ReadOnlyFileSystem rofs_) { rofs = rofs_; fs = null; } // ReadOnlyFileSystem bool stat(string filename, out FileStat stat) { return rofs.stat(filename, stat); } InputStream openForInput(string filename) { InputStream ins = rofs.openForInput(filename); if (ins is null) throw new Exception("Failed to open \"" ~ filename ~ "\""); return ins; } Directory openDir(string path) { return rofs.openDir(path); } InputRange!DirEntry findFiles(string baseDir, bool recursive) { return dlib.filesystem.filesystem.findFiles(rofs, baseDir, recursive); } // FileSystem OutputStream openForOutput(string filename, uint creationFlags = FileSystem.create | FileSystem.truncate) { OutputStream outs = fs.openForOutput(filename, creationFlags); if (outs is null) throw new Exception("Failed to open \"" ~ filename ~ "\" for writing"); return outs; } IOStream openForIO(string filename, uint creationFlags) { IOStream ios = fs.openForIO(filename, creationFlags); if (ios is null) throw new Exception("Failed to open \"" ~ filename ~ "\" for writing"); return ios; } bool createDir(string path, bool recursive) { return fs.createDir(path, recursive); } /* bool move(string path, string newPath) { return fs.move(path, newPath); } */ bool remove(string path, bool recursive) { return fs.remove(path, recursive); } unittest { // TODO: test >4GiB files import std.algorithm; import std.file; alias remove = dlib.filesystem.local.remove; remove("tests/test_data", true); assert(openDir("tests/test_data") is null); assert(createDir("tests/test_data/main", true)); enum dir = "tests"; auto d = openDir(dir); try { chdir(dir); auto expected = dirEntries("", SpanMode.shallow) .filter!(e => e.isFile) .array; size_t i; chdir(".."); foreach (entry; d.contents) { if (entry.isFile) { assert(expected[i] == entry.name); ++i; } } } finally { d.close(); } // OutputStream outp = openForOutput("tests/test_data/main/hello_world.txt", FileSystem.create | FileSystem.truncate); string expected = "Hello, World!\n"; assert(outp); try { assert(outp.writeArray(expected)); } finally { outp.close(); } // InputStream inp = openForInput("tests/test_data/main/hello_world.txt"); assert(inp); try { while (inp.readable) { char[1] buffer; auto have = inp.readBytes(buffer.ptr, buffer.length); assert(buffer[0..have] == expected[0..have]); expected.popFrontN(have); } } finally { inp.close(); } } unittest { import std.algorithm; import std.file; auto expected = dirEntries("", SpanMode.depth) .filter!(e => e.isFile) .filter!(e => e.name.baseName.endsWith(".d")) .map!(e => e.name.replace("\\", "/")) .array; size_t i; foreach (entry; findFiles("", true) .filter!(entry => entry.isFile) .filter!(e => e.name.baseName.globMatch("*.d"))) { FileStat stat_; assert(stat(entry.name, stat_)); // make sure we're getting the expected path assert(expected[i] == entry.name); assert(stat_.sizeInBytes == expected[i].getSize()); //SysTime modificationTime, accessTime; //expected[i].getTimes(accessTime, modificationTime); //assert(modificationTime == stat_.modificationTimestamp); ++i; } } ================================================ FILE: dlib/filesystem/package.d ================================================ /* Copyright (c) 2014-2025 Martin Cejp, Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Abstract FS interface and its implementations for Windows and POSIX filesystems * * Copyright: Martin Cejp, Timur Gafarov 2014-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Martin Cejp, Timur Gafarov */ module dlib.filesystem; public { import dlib.filesystem.filesystem; import dlib.filesystem.local; import dlib.filesystem.stdfs; } ================================================ FILE: dlib/filesystem/posix/common.d ================================================ /* Copyright (c) 2014-2025 Martin Cejp Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ module dlib.filesystem.posix.common; version(Posix) { public { import core.sys.posix.dirent; import core.sys.posix.fcntl; import core.sys.posix.sys.stat; import core.sys.posix.sys.types; import core.sys.posix.unistd; } // Rename stat to stat_ because of method name collision version(linux) { alias lseek = lseek64; alias open = open64; alias stat_ = stat64; } else { alias stat_ = stat; } } ================================================ FILE: dlib/filesystem/posix/directory.d ================================================ /* Copyright (c) 2014-2025 Martin Cejp Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ module dlib.filesystem.posix.directory; version(Posix) { import std.conv; import std.range; import dlib.filesystem.filesystem; import dlib.filesystem.dirrange; import dlib.filesystem.posix.common; class PosixDirectory: Directory { FileSystem fs; DIR* dir; string prefix; this(FileSystem fs, DIR* dir, string prefix) { this.fs = fs; this.dir = dir; this.prefix = prefix; } ~this() { close(); } void close() { if (dir != null) { closedir(dir); dir = null; } } InputRange!DirEntry contents() { if (dir == null) return null; // FIXME: throw an error return new DirRange(delegate bool(out DirEntry de) { dirent entry_buf; dirent* entry; for (;;) { readdir_r(dir, &entry_buf, &entry); if (entry == null) return false; else { string name = to!string(cast(const char*) entry.d_name); if (name == "." || name == "..") continue; de.name = name; de.isFile = (entry.d_type & DT_REG) != 0; de.isDirectory = (entry.d_type & DT_DIR) != 0; return true; } } }); } } } ================================================ FILE: dlib/filesystem/posix/file.d ================================================ /* Copyright (c) 2014-2025 Martin Cejp Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ module dlib.filesystem.posix.file; version (Posix) { import dlib.core.stream; import dlib.core.memory; import dlib.filesystem.filesystem; import dlib.filesystem.posix.common; static import core.sys.posix.unistd; class PosixFile: IOStream { int fd; uint accessFlags; bool eof = false; this(int fd, uint accessFlags) { this.fd = fd; this.accessFlags = accessFlags; } ~this() { close(); } override void close() { if (fd != -1) { core.sys.posix.unistd.close(fd); fd = -1; } } override bool seekable() { return true; } override StreamPos getPosition() { import core.sys.posix.stdio; return lseek(fd, 0, SEEK_CUR); } override bool setPosition(StreamPos pos) { import core.sys.posix.stdio; return lseek(fd, pos, SEEK_SET) == pos; } override StreamSize size() { import core.sys.posix.stdio; auto off = lseek(fd, 0, SEEK_CUR); auto end = lseek(fd, 0, SEEK_END); lseek(fd, off, SEEK_SET); return end; } override bool readable() { return fd != -1 && (accessFlags & FileSystem.read) && !eof; } override size_t readBytes(void* buffer, size_t count) { immutable size_t got = core.sys.posix.unistd.read(fd, buffer, count); if (count > got) eof = true; return got; } override bool writeable() { return fd != -1 && (accessFlags & FileSystem.write); } override size_t writeBytes(const void* buffer, size_t count) { return core.sys.posix.unistd.write(fd, buffer, count); } override void flush() { } } } ================================================ FILE: dlib/filesystem/stdfs.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * GC-free filesystem * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.filesystem.stdfs; import core.stdc.stdio; import std.file; import std.string; import std.datetime; import dlib.core.memory; import dlib.core.stream; import dlib.container.dict; import dlib.container.array; import dlib.text.str; import dlib.filesystem.filesystem; version(Posix) { import dlib.filesystem.posix.common; import dlib.filesystem.stdposixdir; } version(Windows) { import std.stdio; import dlib.filesystem.windows.common; import dlib.filesystem.stdwindowsdir; } import dlib.text.utils; import dlib.text.utf16; version(Windows) { extern(C) int _wmkdir(const wchar*); extern(C) int _wremove(const wchar*); extern(Windows) int RemoveDirectoryW(const wchar*); } /// InputStream that wraps FILE class StdInFileStream: InputStream { FILE* file; StreamSize _size; bool eof; this(FILE* file) { this.file = file; fseek(file, 0, SEEK_END); _size = ftell(file); fseek(file, 0, SEEK_SET); eof = false; } ~this() { fclose(file); } StreamPos getPosition() @property { return ftell(file); } bool setPosition(StreamPos p) { import core.stdc.config: c_long; return !fseek(file, cast(c_long)p, SEEK_SET); } StreamSize size() { return _size; } void close() { fclose(file); } bool seekable() { return true; } bool readable() { return !eof; } size_t readBytes(void* buffer, size_t count) { auto bytesRead = fread(buffer, 1, count, file); if (count > bytesRead) eof = true; return bytesRead; } } /// OutputStream that wraps FILE class StdOutFileStream: OutputStream { FILE* file; bool _writeable; this(FILE* file) { this.file = file; this._writeable = true; } ~this() { fclose(file); } StreamPos getPosition() @property { return 0; } bool setPosition(StreamPos pos) { return false; } StreamSize size() { return 0; } void close() { fclose(file); } bool seekable() { return false; } void flush() { fflush(file); } bool writeable() { return _writeable; } size_t writeBytes(const void* buffer, size_t count) { size_t res = fwrite(buffer, 1, count, file); if (res != count) _writeable = false; return res; } } /// IOStream that wraps FILE class StdIOStream: IOStream { FILE* file; StreamSize _size; bool _eof; bool _writeable; this(FILE* file) { this.file = file; this._writeable = true; fseek(file, 0, SEEK_END); this._size = ftell(file); fseek(file, 0, SEEK_SET); this._eof = false; } ~this() { fclose(file); } StreamPos getPosition() @property { return ftell(file); } bool setPosition(StreamPos p) { import core.stdc.config : c_long; return !fseek(file, cast(c_long)p, SEEK_SET); } StreamSize size() { return _size; } void close() { fclose(file); } bool seekable() { return true; } bool readable() { return !_eof; } size_t readBytes(void* buffer, size_t count) { auto bytesRead = fread(buffer, 1, count, file); if (count > bytesRead) _eof = true; return bytesRead; } void flush() { fflush(file); } bool writeable() { return _writeable; } size_t writeBytes(const void* buffer, size_t count) { size_t res = fwrite(buffer, 1, count, file); if (res != count) _writeable = false; return res; } } /// FileSystem that wraps libc filesystem functions + some Posix and WinAPI parts for additional functionality class StdFileSystem: FileSystem { Dict!(Directory, string) openedDirs; Array!string openedDirPaths; this() { openedDirs = New!(Dict!(Directory, string)); } ~this() { foreach(k, v; openedDirs) Delete(v); Delete(openedDirs); foreach(p; openedDirPaths) Delete(p); openedDirPaths.free(); } bool stat(string filename, out FileStat stat) { if (std.file.exists(filename)) { with(stat) { version (Posix) { stat_t st; String filenamez = String(filename); stat_(filenamez.ptr, &st); filenamez.free(); isFile = S_ISREG(st.st_mode); isDirectory = S_ISDIR(st.st_mode); sizeInBytes = st.st_size; creationTimestamp = SysTime(unixTimeToStdTime(st.st_ctime)); auto modificationStdTime = unixTimeToStdTime(st.st_mtime); static if (is(typeof(st.st_mtimensec))) { modificationStdTime += st.st_mtimensec / 100; } modificationTimestamp = SysTime(modificationStdTime); if ((st.st_mode & S_IRUSR) | (st.st_mode & S_IRGRP) | (st.st_mode & S_IROTH)) permissions |= PRead; if ((st.st_mode & S_IWUSR) | (st.st_mode & S_IWGRP) | (st.st_mode & S_IWOTH)) permissions |= PWrite; if ((st.st_mode & S_IXUSR) | (st.st_mode & S_IXGRP) | (st.st_mode & S_IXOTH)) permissions |= PExecute; } else version(Windows) { wchar[] filename_utf16 = convertUTF8toUTF16(filename, true); WIN32_FILE_ATTRIBUTE_DATA data; if (!GetFileAttributesExW(filename_utf16.ptr, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &data)) return false; if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = true; else isFile = true; sizeInBytes = (cast(FileSize) data.nFileSizeHigh << 32) | data.nFileSizeLow; creationTimestamp = SysTime(FILETIMEToStdTime(&data.ftCreationTime)); modificationTimestamp = SysTime(FILETIMEToStdTime(&data.ftLastWriteTime)); permissions = 0; PACL pacl; PSECURITY_DESCRIPTOR secDesc; TRUSTEE_W trustee; trustee.pMultipleTrustee = null; trustee.MultipleTrusteeOperation = MULTIPLE_TRUSTEE_OPERATION.NO_MULTIPLE_TRUSTEE; trustee.TrusteeForm = TRUSTEE_FORM.TRUSTEE_IS_NAME; trustee.TrusteeType = TRUSTEE_TYPE.TRUSTEE_IS_UNKNOWN; trustee.ptstrName = cast(wchar*)"CURRENT_USER"w.ptr; GetNamedSecurityInfoW(filename_utf16.ptr, SE_OBJECT_TYPE.SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, null, null, &pacl, null, &secDesc); if (pacl) { uint access; GetEffectiveRightsFromAcl(pacl, &trustee, &access); if (access & ACTRL_FILE_READ) permissions |= PRead; if ((access & ACTRL_FILE_WRITE) && !(data.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) permissions |= PWrite; if (access & ACTRL_FILE_EXECUTE) permissions |= PExecute; } Delete(filename_utf16); } else { isFile = std.file.isFile(filename); isDirectory = std.file.isDir(filename); sizeInBytes = std.file.getSize(filename); getTimes(filename, modificationTimestamp, modificationTimestamp); } } return true; } else return false; } StdInFileStream openForInput(string filename) { version(Posix) { String filenamez = String(filename); FILE* file = fopen(filenamez.ptr, "rb"); filenamez.free(); } version(Windows) { wchar[] filename_utf16 = convertUTF8toUTF16(filename, true); wchar[] mode_utf16 = convertUTF8toUTF16("rb", true); FILE* file = _wfopen(filename_utf16.ptr, mode_utf16.ptr); Delete(filename_utf16); Delete(mode_utf16); } return New!StdInFileStream(file); } StdOutFileStream openForOutput(string filename, uint creationFlags = FileSystem.create) { version(Posix) { String filenamez = String(filename); FILE* file = fopen(filenamez.ptr, "wb"); filenamez.free(); } version(Windows) { wchar[] filename_utf16 = convertUTF8toUTF16(filename, true); wchar[] mode_utf16 = convertUTF8toUTF16("wb", true); FILE* file = _wfopen(filename_utf16.ptr, mode_utf16.ptr); Delete(filename_utf16); Delete(mode_utf16); } return New!StdOutFileStream(file); } StdIOStream openForIO(string filename, uint creationFlags = FileSystem.create) { version(Posix) { String filenamez = String(filename); FILE* file = fopen(filename.ptr, "rb+"); filenamez.free(); } version(Windows) { wchar[] filename_utf16 = convertUTF8toUTF16(filename, true); wchar[] mode_utf16 = convertUTF8toUTF16("rb+", true); FILE* file = _wfopen(filename_utf16.ptr, mode_utf16.ptr); Delete(filename_utf16); Delete(mode_utf16); } return New!StdIOStream(file); } Directory openDir(string path) { FileStat ps; if (!stat(path, ps)) return null; if (!ps.isDirectory) return null; if (path in openedDirs) { Directory d = openedDirs[path]; Delete(d); } Directory dir; version(Posix) { dir = New!StdPosixDirectory(path); } version(Windows) { string s = catStr(path, "\\*.*"); wchar[] ws = convertUTF8toUTF16(s, true); Delete(s); dir = New!StdWindowsDirectory(ws.ptr); } auto p = New!(char[])(path.length); p[] = path[]; openedDirPaths.append(cast(string)p); openedDirs[cast(string)p] = dir; return dir; } bool createDir(string path, bool recursive = true) { version(Posix) { String pathz = String(path); int res = mkdir(pathz.ptr, 777); pathz.free(); return (res == 0); } version(Windows) { wchar[] wp = convertUTF8toUTF16(path, true); int res = _wmkdir(wp.ptr); Delete(wp); return (res == 0); } } bool remove(string path, bool recursive = true) { version(Posix) { String pathz = String(path); int res = core.stdc.stdio.remove(pathz.ptr); pathz.free(); return (res == 0); } version(Windows) { import std.stdio; bool res; if (std.file.isDir(path)) { if (recursive) foreach(e; openDir(path).contents) { string path2 = catStr(path, "\\"); string path3 = catStr(path2, e.name); Delete(path2); this.remove(path3, recursive); Delete(path3); } wchar[] wp = convertUTF8toUTF16(path, true); res = RemoveDirectoryW(wp.ptr) != 0; Delete(wp); } else { wchar[] wp = convertUTF8toUTF16(path, true); res = _wremove(wp.ptr) == 0; Delete(wp); } return res; } } } /// version(Windows) unittest { StdFileSystem fs = New!StdFileSystem(); string path = "tests/stdfs"; FileStat ps; if (fs.stat(path, ps)) { fs.remove(path, true); assert(fs.openDir(path) is null); } assert(fs.createDir(path, true)); string filename = "tests/stdfs/hello_world.txt"; OutputStream outp = fs.openForOutput(filename, FileSystem.create | FileSystem.truncate); assert(outp); string data = "Hello, World!\n"; assert(outp.writeArray(data)); outp.close(); InputStream inp = fs.openForInput(filename); assert(inp); string text = readText(inp); assert(text == data); inp.close(); FileStat s; assert(fs.stat(filename, s)); assert(s.isFile); assert(s.sizeInBytes == 14); } /// Reads string from InputStream and stores it in unmanaged memory string readText(InputStream istrm) { ubyte[] arr = New!(ubyte[])(cast(size_t)istrm.size); istrm.fillArray(arr); istrm.setPosition(0); return cast(string)arr; } /// Reads struct from InputStream T readStruct(T)(InputStream istrm) if (is(T == struct)) { T res; istrm.readBytes(&res, T.sizeof); return res; } enum MAX_PATH_LEN = 4096; struct PathBuilder { // TODO: use dlib.text.unmanaged.String instead char[MAX_PATH_LEN] str; uint length = 0; void append(string s) { if (length && str[length-1] != '/') { str[length] = '/'; length++; } str[length..length+s.length] = s[]; length += s.length; } string toString() return { if (length) return cast(string)(str[0..length]); else return ""; } } struct RecursiveFileIterator { PathBuilder pb; ReadOnlyFileSystem rofs; string directory; bool rec; this(ReadOnlyFileSystem fs, string dir, bool recursive) { rofs = fs; directory = dir; pb.append(dir); rec = recursive; } int opApply(scope int delegate(string path, ref dlib.filesystem.filesystem.DirEntry) dg) { int result = 0; if (!rofs) return 0; foreach(e; rofs.openDir(directory).contents) { uint pathPos = pb.length; pb.append(e.name); string oldPath = directory; directory = pb.toString; result = dg(directory, e); if (result) break; if (e.isDirectory && rec) result = opApply(dg); directory = oldPath; pb.length = pathPos; if (result) break; } return 0; } int opApply(scope int delegate(ref dlib.filesystem.filesystem.DirEntry) dg) { int result = 0; auto dir = rofs.openDir(directory); foreach(e; dir.contents) { uint pathPos = pb.length; pb.append(e.name); string oldPath = directory; directory = pb.toString; result = dg(e); if (result) break; if (e.isDirectory) result = opApply(dg); directory = oldPath; pb.length = pathPos; if (result) break; } return 0; } } /// Enumerate directory contents, optionally recursive RecursiveFileIterator traverseDir(ReadOnlyFileSystem rofs, string baseDir, bool recursive) { FileStat s; if (!rofs.stat(baseDir, s)) return RecursiveFileIterator(null, baseDir, recursive); else return RecursiveFileIterator(rofs, baseDir, recursive); } ================================================ FILE: dlib/filesystem/stdposixdir.d ================================================ /* Copyright (c) 2016-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ module dlib.filesystem.stdposixdir; import std.string; import std.conv; import std.range; import core.stdc.string; version(Posix) { import core.sys.posix.dirent; } import dlib.core.memory; import dlib.filesystem.filesystem; import dlib.text.str; version(Posix): class StdPosixDirEntryRange: InputRange!(DirEntry) { DIR* dir; dirent* de = null; DirEntry currentEntry; bool _empty = false; this(DIR* dir) { this.dir = dir; readNextEntry(); } void readNextEntry() { de = readdir(dir); if (de) { string name = getFileName(de); if (name == "." || name == "..") readNextEntry(); else { bool isFile = (de.d_type == DT_REG); bool isDir = (de.d_type == DT_DIR); currentEntry = DirEntry(getFileName(de), isFile, isDir); } } else _empty = true; } string getFileName(dirent* d) { return cast(string)d.d_name[0..strlen(d.d_name.ptr)]; } void reset() { rewinddir(dir); _empty = false; } override DirEntry front() { return currentEntry; } override void popFront() { readNextEntry(); } override DirEntry moveFront() { readNextEntry(); return currentEntry; } override bool empty() { return _empty; } int opApply(scope int delegate(DirEntry) dg) { while(!_empty) { dg(currentEntry); readNextEntry(); } return 0; } int opApply(scope int delegate(size_t, DirEntry) dg) { size_t i = 0; while(!_empty) { dg(i, currentEntry); readNextEntry(); i++; } return 0; } } class StdPosixDirectory: Directory { DIR* dir; StdPosixDirEntryRange drange; this(string path) { String pathz = String(path); dir = opendir(pathz.ptr); pathz.free(); } ~this() { if (drange) { Delete(drange); drange = null; } close(); } void close() { if (dir) { closedir(dir); dir = null; } } StdPosixDirEntryRange contents() { if (drange) drange.reset(); else drange = New!StdPosixDirEntryRange(dir); return drange; } } ================================================ FILE: dlib/filesystem/stdwindowsdir.d ================================================ /* Copyright (c) 2016-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ module dlib.filesystem.stdwindowsdir; import std.string; import std.conv; import std.range; import core.stdc.string; import core.stdc.wchar_; version(Windows) { import core.sys.windows.windows; } import dlib.core.memory; import dlib.filesystem.filesystem; import dlib.text.utf16; version(Windows): string unmanagedStrFromCStrW(wchar* cstr) { return cast(string)convertUTF16ztoUTF8(cstr); } class StdWindowsDirEntryRange: InputRange!(DirEntry) { HANDLE hFind = INVALID_HANDLE_VALUE; WIN32_FIND_DATAW findData; DirEntry frontEntry; bool _empty = false; wchar* path; bool initialized = false; this(wchar* cwstr) { this.path = cwstr; } ~this() { if (frontEntry.name.length) Delete(frontEntry.name); close(); } import std.stdio; bool advance() { bool success = false; if (frontEntry.name.length) Delete(frontEntry.name); if (!initialized) { hFind = FindFirstFileW(path, &findData); initialized = true; if (hFind != INVALID_HANDLE_VALUE) success = true; string name = unmanagedStrFromCStrW(findData.cFileName.ptr); if (name == "." || name == "..") { success = false; Delete(name); } else { bool isDir = cast(bool)(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); bool isFile = !isDir; frontEntry = DirEntry(name, isFile, isDir); } } if (!success && hFind != INVALID_HANDLE_VALUE) { string name; while(!success) { auto r = FindNextFileW(hFind, &findData); if (!r) break; name = unmanagedStrFromCStrW(findData.cFileName.ptr); if (name != "." && name != "..") success = true; else Delete(name); } if (success) { bool isDir = cast(bool)(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); bool isFile = !isDir; frontEntry = DirEntry(name, isFile, isDir); } } if (!success) { FindClose(hFind); hFind = INVALID_HANDLE_VALUE; } return success; } override DirEntry front() { return frontEntry; } override void popFront() { _empty = !advance(); } override DirEntry moveFront() { _empty = !advance(); return frontEntry; } override bool empty() { return _empty; } int opApply(scope int delegate(DirEntry) dg) { int result = 0; for (size_t i = 0; !empty; i++) { popFront(); if (!empty()) result = dg(frontEntry); if (result != 0) break; } return result; } int opApply(scope int delegate(size_t, DirEntry) dg) { int result = 0; for (size_t i = 0; !empty; i++) { popFront(); if (!empty()) result = dg(i, frontEntry); if (result != 0) break; } return result; } void reset() { close(); } void close() { if (hFind != INVALID_HANDLE_VALUE) { FindClose(hFind); hFind = INVALID_HANDLE_VALUE; } initialized = false; _empty = false; } } class StdWindowsDirectory: Directory { StdWindowsDirEntryRange drange; wchar* path; this(wchar* cwstr) { path = cwstr; drange = New!StdWindowsDirEntryRange(path); } void close() { drange.close(); } StdWindowsDirEntryRange contents() { if (drange) drange.reset(); return drange; } ~this() { Delete(drange); drange = null; Delete(path); } } ================================================ FILE: dlib/filesystem/windows/common.d ================================================ /* Copyright (c) 2014-2025 Martin Cejp Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ module dlib.filesystem.windows.common; public { version(Windows) { import core.stdc.wchar_; import core.sys.windows.windows; import core.sys.windows.accctrl; import core.sys.windows.aclapi; import std.utf; import std.windows.syserror; enum DWORD NO_ERROR = 0; enum DWORD INVALID_FILE_ATTRIBUTES = cast(DWORD)0xFFFFFFFF; static T wenforce(T)(T cond, string str = null) { import std.array; if (cond) return cond; string err = sysErrorString(GetLastError()); throw new Exception(!str.empty ? (str ~ ": " ~ err) : err); } } } ================================================ FILE: dlib/filesystem/windows/directory.d ================================================ /* Copyright (c) 2014-2025 Martin Cejp Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ module dlib.filesystem.windows.directory; version(Windows) { import dlib.filesystem.filesystem; import dlib.filesystem.dirrange; import dlib.filesystem.windows.common; import std.conv; import std.range; class WindowsDirectory: Directory { FileSystem fs; HANDLE find = INVALID_HANDLE_VALUE; string prefix; WIN32_FIND_DATAW entry; bool entryValid = false; this(FileSystem fs, string path, string prefix) { this.fs = fs; this.prefix = prefix; find = FindFirstFileW(toUTF16z(path ~ `\*.*`), &entry); if (find != INVALID_HANDLE_VALUE) entryValid = true; } ~this() { close(); } void close() { if (find != INVALID_HANDLE_VALUE) { FindClose(find); find = INVALID_HANDLE_VALUE; } } InputRange!DirEntry contents() { return new DirRange(delegate bool(out DirEntry de) { for (;;) { WIN32_FIND_DATAW* entry = nextEntry(); if (entry == null) return false; else { size_t len = wcslen(entry.cFileName.ptr); string name = to!string(entry.cFileName[0..len]); if (name == "." || name == "..") continue; de.name = name; if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) de.isDirectory = true; else de.isFile = true; return true; } } }); } private WIN32_FIND_DATAW* nextEntry() { if (entryValid) { entryValid = false; return &entry; } if (find == INVALID_HANDLE_VALUE || !FindNextFileW(find, &entry)) return null; return &entry; } } } ================================================ FILE: dlib/filesystem/windows/file.d ================================================ /* Copyright (c) 2014-2025 Martin Cejp Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ module dlib.filesystem.windows.file; version(Windows) { import dlib.filesystem.filesystem; import dlib.filesystem.windows.common; import dlib.core.stream; import dlib.core.memory; class WindowsFile: IOStream { HANDLE handle; uint accessFlags; bool eof = false; this(HANDLE handle, uint accessFlags) { this.handle = handle; this.accessFlags = accessFlags; } ~this() { close(); } override void close() { if (handle != INVALID_HANDLE_VALUE) { CloseHandle(handle); handle = INVALID_HANDLE_VALUE; } } override bool seekable() { return true; } override StreamPos getPosition() { LONG pos_high = 0; LONG pos_low = SetFilePointer(handle, 0, &pos_high, FILE_CURRENT); wenforce(pos_low != INVALID_SET_FILE_POINTER || GetLastError() == NO_ERROR); return cast(StreamPos) pos_high << 32 | pos_low; } override bool setPosition(StreamPos pos) { LONG pos_high = cast(LONG)(pos >> 32); if (SetFilePointer(handle, cast(LONG) pos, &pos_high, FILE_BEGIN) == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) return false; else return true; } override StreamSize size() { DWORD size_high; DWORD size_low = GetFileSize(handle, &size_high); wenforce(size_low != INVALID_FILE_SIZE || GetLastError() == NO_ERROR); return cast(StreamPos) size_high << 32 | size_low; } override bool readable() { return handle != INVALID_HANDLE_VALUE && (accessFlags & FileSystem.read) && !eof; } override size_t readBytes(void* buffer, size_t count) { // TODO: make sure that count fits in a DWORD DWORD dwCount = cast(DWORD) count; DWORD dwGot = void; wenforce(ReadFile(handle, buffer, dwCount, &dwGot, null)); if (dwCount > dwGot) eof = true; return dwGot; } override bool writeable() { return handle != INVALID_HANDLE_VALUE && (accessFlags & FileSystem.write); } override size_t writeBytes(const void* buffer, size_t count) { // TODO: make sure that count fits in a DWORD DWORD dwCount = cast(DWORD) count; DWORD dwGot = void; wenforce(WriteFile(handle, buffer, dwCount, &dwGot, null)); return dwGot; } override void flush() { } } } ================================================ FILE: dlib/geometry/aabb.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.geometry.aabb; import std.math; import std.algorithm; import dlib.math.vector; import dlib.geometry.sphere; import dlib.geometry.intersection; /// Axis-aligned bounding box struct AABB { Vector3f center; Vector3f size; Vector3f pmin, pmax; this(Vector3f newPosition, Vector3f newSize) { center = newPosition; size = newSize; pmin = center - size; pmax = center + size; } @property float topHeight() { return (center.y + size.y); } @property float bottomHeight() { return (center.y - size.y); } Vector3f closestPoint(Vector3f point) { Vector3f closest; closest.x = (point.x < pmin.x)? pmin.x : ((point.x > pmax.x)? pmax.x : point.x); closest.y = (point.y < pmin.y)? pmin.y : ((point.y > pmax.y)? pmax.y : point.y); closest.z = (point.z < pmin.z)? pmin.z : ((point.z > pmax.z)? pmax.z : point.z); return closest; } bool containsPoint(Vector3f point) { return !(point.x < pmin.x || point.x > pmax.x || point.y < pmin.y || point.y > pmax.y || point.z < pmin.z || point.z > pmax.z); } bool intersectsAABB(AABB b) { Vector3f t = b.center - center; return fabs(t.x) <= (size.x + b.size.x) && fabs(t.y) <= (size.y + b.size.y) && fabs(t.z) <= (size.z + b.size.z); } deprecated("use dlib.geometry.intersection.intrSphereVsAABB instead") bool intersectsSphere( Sphere sphere, out Vector3f collisionNormal, out float penetrationDepth) { Intersection intr = intrSphereVsAABB(sphere, this); collisionNormal = -intr.normal; penetrationDepth = intr.penetrationDepth; return intr.fact; } private bool intersectsRaySlab( float slabmin, float slabmax, float raystart, float rayend, ref float tbenter, ref float tbexit) { float raydir = rayend - raystart; if (fabs(raydir) < 1.0e-9f) { if (raystart < slabmin || raystart > slabmax) return false; else return true; } float tsenter = (slabmin - raystart) / raydir; float tsexit = (slabmax - raystart) / raydir; if (tsenter > tsexit) { swap(tsenter, tsexit); } if (tbenter > tsexit || tsenter > tbexit) { return false; } else { tbenter = max(tbenter, tsenter); tbexit = min(tbexit, tsexit); return true; } } bool intersectsSegment( Vector3f segStart, Vector3f segEnd, ref float intersectionTime) { float tenter = 0.0f, texit = 1.0f; if (!intersectsRaySlab(pmin.x, pmax.x, segStart.x, segEnd.x, tenter, texit)) return false; if (!intersectsRaySlab(pmin.y, pmax.y, segStart.y, segEnd.y, tenter, texit)) return false; if (!intersectsRaySlab(pmin.z, pmax.z, segStart.z, segEnd.z, tenter, texit)) return false; intersectionTime = tenter; return true; } } /// unittest { import dlib.math.utils; AABB aabb1 = AABB(Vector3f(0, 0, 0), Vector3f(1, 1, 1)); AABB aabb2 = AABB(Vector3f(0.5, 0.5, 0.5), Vector3f(1, 1, 1)); AABB aabb3 = AABB(Vector3f(3, 0, 0), Vector3f(1, 1, 1)); assert(aabb1.containsPoint(Vector3f(0, 0, 0))); assert(!aabb3.containsPoint(Vector3f(0, 0, 0))); assert(aabb1.intersectsAABB(aabb2)); assert(!aabb1.intersectsAABB(aabb3)); Vector3f segStart = Vector3f(0, 0, 3); Vector3f segEnd = Vector3f(0, 0, -3); float segLength = distance(segStart, segEnd); float t; assert(aabb1.intersectsSegment(Vector3f(0, 0, 3), Vector3f(0, 0, -3), t)); assert(isConsiderZero(t * segLength - 2.0f)); assert(!aabb1.intersectsSegment(Vector3f(5, 0, 3), Vector3f(5, 0, -3), t)); } /// Creates AABB from minimum and maximum points AABB boxFromMinMaxPoints(Vector3f mi, Vector3f ma) { AABB box; box.pmin = mi; box.pmax = ma; box.center = (box.pmax + box.pmin) * 0.5f; box.size = box.pmax - box.center; box.size.x = abs(box.size.x); box.size.y = abs(box.size.y); box.size.z = abs(box.size.z); return box; } /// unittest { import dlib.math.utils; Vector3f pmin = Vector3f(-1, -1, -1); Vector3f pmax = Vector3f(1, 1, 1); AABB aabb = boxFromMinMaxPoints(pmin, pmax); assert(isAlmostZero(aabb.center)); assert(isAlmostZero(aabb.size - Vector3f(1, 1, 1))); assert(isAlmostZero(aabb.pmin - pmin)); assert(isAlmostZero(aabb.pmax - pmax)); assert(isConsiderZero(aabb.topHeight - 1.0f)); assert(isConsiderZero(aabb.bottomHeight + 1.0f)); assert(isAlmostZero(aabb.closestPoint(Vector3f(2.0f, 0.0f, 0.0f)) - Vector3f(1.0f, 0.0f, 0.0f))); } ================================================ FILE: dlib/geometry/frustum.d ================================================ /* Copyright (c) 2014-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Timur Gafarov 2014-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov, Andrey Penechko, Roman Vlasov */ module dlib.geometry.frustum; import std.math; import dlib.math.vector; import dlib.math.matrix; import dlib.geometry.plane; import dlib.geometry.aabb; import dlib.geometry.sphere; /// Frustum object struct Frustum { union { Plane[6] planes; struct { Plane leftPlane; Plane rightPlane; Plane bottomPlane; Plane topPlane; Plane farPlane; Plane nearPlane; } } this(Matrix4x4f mvp) { fromMVP(mvp); } void fromMVP(Matrix4x4f mvp) { leftPlane.a = mvp[3] + mvp[0]; leftPlane.b = mvp[7] + mvp[4]; leftPlane.c = mvp[11] + mvp[8]; leftPlane.d = mvp[15] + mvp[12]; leftPlane.normalize(); leftPlane.vectorof = -leftPlane.vectorof; rightPlane.a = mvp[3] - mvp[0]; rightPlane.b = mvp[7] - mvp[4]; rightPlane.c = mvp[11] - mvp[8]; rightPlane.d = mvp[15] - mvp[12]; rightPlane.normalize(); rightPlane.vectorof = -rightPlane.vectorof; bottomPlane.a = mvp[3] + mvp[1]; bottomPlane.b = mvp[7] + mvp[5]; bottomPlane.c = mvp[11] + mvp[9]; bottomPlane.d = mvp[15] + mvp[13]; bottomPlane.normalize(); bottomPlane.vectorof = -bottomPlane.vectorof; topPlane.a = mvp[3] - mvp[1]; topPlane.b = mvp[7] - mvp[5]; topPlane.c = mvp[11] - mvp[9]; topPlane.d = mvp[15] - mvp[13]; topPlane.normalize(); topPlane.vectorof = -topPlane.vectorof; farPlane.a = mvp[3] - mvp[2]; farPlane.b = mvp[7] - mvp[6]; farPlane.c = mvp[11] - mvp[10]; farPlane.d = mvp[15] - mvp[14]; farPlane.normalize(); farPlane.vectorof = -farPlane.vectorof; nearPlane.a = mvp[3] + mvp[2]; nearPlane.b = mvp[7] + mvp[6]; nearPlane.c = mvp[11] + mvp[10]; nearPlane.d = mvp[15] + mvp[14]; nearPlane.normalize(); nearPlane.vectorof = -nearPlane.vectorof; } bool containsPoint(Vector3f point, bool checkNearPlane = false) { int res = 0; foreach(i, ref p; planes) { if (i == 5 && !checkNearPlane) break; if (p.distance(point) <= 0.0f) res++; } return (res == (checkNearPlane? 6 : 5)); } bool intersectsSphere(Sphere sphere) { float d; foreach(i, ref p; planes) { d = p.distance(sphere.center); if (d > sphere.radius) return false; } return true; } bool intersectsAABB( AABB aabb, bool checkBoundariesOnly = false, bool checkNearPlane = true) { bool result = !checkBoundariesOnly; // Inside foreach (i, ref plane; planes) { if (i == 5 && !checkNearPlane) break; float d = dot(aabb.center, -plane.normal); float r = aabb.size.x * abs(plane.normal.x) + aabb.size.y * abs(plane.normal.y) + aabb.size.z * abs(plane.normal.z); float d_p_r = d + r; float d_m_r = d - r; if (d_p_r < plane.d) { result = false; // Outside break; } else if(d_m_r < plane.d) result = true; // Intersect } return result; } } /// unittest { import dlib.math.transformation; Matrix4x4f mvp = perspectiveMatrix(60.0f, 16.0f/9.0f, 0.1f, 1000.0f); Frustum f = Frustum(mvp); assert(f.containsPoint(Vector3f(0, 0, -10))); Sphere s = Sphere(Vector3f(0, 0, 10), 1); assert(!f.intersectsSphere(s)); AABB aabb = AABB(Vector3f(0, 0, 0), Vector3f(1, 1, 1)); assert(f.intersectsAABB(aabb)); } ================================================ FILE: dlib/geometry/intersection.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.geometry.intersection; import std.math; import dlib.math.vector; import dlib.math.utils; import dlib.math.transformation; import dlib.geometry.aabb; import dlib.geometry.obb; import dlib.geometry.plane; import dlib.geometry.sphere; import dlib.geometry.triangle; /// Stores intersection data struct Intersection { bool fact = false; Vector3f point; Vector3f normal; float penetrationDepth; } /// Checks two spheres for intersection Intersection intrSphereVsSphere(ref Sphere sphere1, ref Sphere sphere2) { Intersection res; res.fact = false; float d = distance(sphere1.center, sphere2.center); float sumradius = sphere1.radius + sphere2.radius; if (d < sumradius) { res.penetrationDepth = sumradius - d; res.normal = (sphere1.center - sphere2.center).normalized; res.point = sphere2.center + res.normal * sphere2.radius; res.fact = true; } return res; } /// unittest { Sphere sphere1 = Sphere(Vector3f(0, 0, 0), 1.0f); Sphere sphere2 = Sphere(Vector3f(1.9f, 0, 0), 1.0f); Intersection isec = intrSphereVsSphere(sphere1, sphere2); assert(isec.fact); assert(isConsiderZero(isec.penetrationDepth - 0.1f)); assert(isAlmostZero(isec.point - Vector3f(0.9f, 0.0f, 0.0f))); assert(isAlmostZero(isec.normal - Vector3f(-1.0f, 0.0f, 0.0f))); } /// Checks sphere and plane for intersection Intersection intrSphereVsPlane(ref Sphere sphere, ref Plane plane) { Intersection res; res.fact = false; float q = plane.normal.dot(sphere.center - plane.d).abs; if (q <= sphere.radius) { res.penetrationDepth = sphere.radius - q; res.normal = plane.normal; res.point = sphere.center - res.normal * sphere.radius; res.fact = true; } return res; } /// unittest { Sphere sphere = Sphere(Vector3f(0, 0.9f, 0), 1.0f); Plane plane = Plane(Vector3f(0, 1, 0), 0.0f); Intersection isec = intrSphereVsPlane(sphere, plane); assert(isec.fact); assert(isConsiderZero(isec.penetrationDepth - 0.1f)); assert(isAlmostZero(isec.point - Vector3f(0.0f, -0.1f, 0.0f))); assert(isAlmostZero(isec.normal - Vector3f(0.0f, 1.0f, 0.0f))); } private void measureSphereAndTriVert( Vector3f center, float radius, ref Intersection result, Triangle tri, int whichVert) { Vector3f diff = center - tri.v[whichVert]; float len = diff.length; float penetrate = radius - len; if (penetrate > 0.0f) { result.fact = true; result.penetrationDepth = penetrate; result.normal = diff * (1.0f / len); result.point = center - result.normal * radius; } } void measureSphereAndTriEdge( Vector3f center, float radius, ref Intersection result, Triangle tri, int whichEdge) { static int[] nextDim1 = [1, 2, 0]; static int[] nextDim2 = [2, 0, 1]; int whichVert0, whichVert1; whichVert0 = whichEdge; whichVert1 = nextDim1[whichEdge]; float penetrate; Vector3f dir = tri.edges[whichEdge]; float edgeLen = dir.length; if (isConsiderZero(edgeLen)) dir = Vector3f(0.0f, 0.0f, 0.0f); else dir *= (1.0f / edgeLen); Vector3f vert2Point = center - tri.v[whichVert0]; float dot = dir.dot(vert2Point); Vector3f project = tri.v[whichVert0] + dir * dot; if (dot > 0.0f && dot < edgeLen) { Vector3f diff = center - project; float len = diff.length; penetrate = radius - len; if (penetrate > 0.0f && penetrate < result.penetrationDepth && penetrate < radius) { result.fact = true; result.penetrationDepth = penetrate; result.normal = diff * (1.0f / len); result.point = center - result.normal * radius; } } } /// Checks sphere and triangle for intersection Intersection intrSphereVsTriangle(ref Sphere sphere, ref Triangle tri) { Intersection result; result.point = Vector3f(0.0f, 0.0f, 0.0f); result.normal = Vector3f(0.0f, 0.0f, 0.0f); result.penetrationDepth = 1.0e5f; result.fact = false; float distFromPlane = tri.normal.dot(sphere.center) - tri.d; float factor = 1.0f; if (distFromPlane < 0.0f) factor = -1.0f; float penetrated = sphere.radius - distFromPlane * factor; if (penetrated <= 0.0f) return result; Vector3f contactB = sphere.center - tri.normal * distFromPlane; int pointInside = tri.isPointInside(contactB); if (pointInside == -1) // inside the triangle { result.penetrationDepth = penetrated; result.point = sphere.center - tri.normal * factor * sphere.radius; //on the sphere result.fact = true; result.normal = tri.normal * factor; return result; } switch (pointInside) { case 0: measureSphereAndTriVert(sphere.center, sphere.radius, result, tri, 0); break; case 1: measureSphereAndTriEdge(sphere.center, sphere.radius, result, tri, 0); break; case 2: measureSphereAndTriVert(sphere.center, sphere.radius, result, tri, 1); break; case 3: measureSphereAndTriEdge(sphere.center, sphere.radius, result, tri, 1); break; case 4: measureSphereAndTriVert(sphere.center, sphere.radius, result, tri, 2); break; case 5: measureSphereAndTriEdge(sphere.center, sphere.radius, result, tri, 2); break; default: break; } return result; } /// unittest { Sphere sphere = Sphere(Vector3f(0, 0.9f, 0), 1.0f); Triangle tri; tri.v = [ Vector3f(0.5f, 0, -0.5f), Vector3f(-0.5f, 0, -0.5f), Vector3f(0, 0, 0.5f) ]; tri.normal = Vector3f(0, 1, 0); tri.d = 0.0f; Intersection isec = intrSphereVsTriangle(sphere, tri); assert(isec.fact); assert(isConsiderZero(isec.penetrationDepth - 0.1f)); assert(isAlmostZero(isec.point - Vector3f(0.0f, -0.1f, 0.0f))); assert(isAlmostZero(isec.normal - Vector3f(0.0f, 1.0f, 0.0f))); } /// Checks sphere and AABB for intersection Intersection intrSphereVsAABB(ref Sphere sphere, ref AABB aabb) { Intersection result; result.penetrationDepth = 0.0f; result.normal = Vector3f(0.0f, 0.0f, 0.0f); result.fact = false; if (aabb.containsPoint(sphere.center)) { result.penetrationDepth = distance(aabb.center, sphere.center); result.normal = (aabb.center - sphere.center) / result.penetrationDepth; result.point = sphere.center + result.normal * sphere.radius; result.fact = true; return result; } else { Vector3f closest = aabb.closestPoint(sphere.center); Vector3f delta = closest - sphere.center; float distSquared = delta.lengthsqr(); if (distSquared > sphere.radius * sphere.radius) return result; result.fact = true; float dist = sqrt(distSquared); result.penetrationDepth = sphere.radius - dist; result.normal = delta / dist; result.point = sphere.center + result.normal * sphere.radius; return result; } } /// unittest { Sphere sphere = Sphere(Vector3f(1.5f, 0.0f, 0.0f), 1.0f); AABB aabb = AABB(Vector3f(0, 0, 0), Vector3f(1, 1, 1)); Intersection intr = intrSphereVsAABB(sphere, aabb); assert(intr.fact); assert(isAlmostZero(intr.normal - Vector3f(-1.0f, 0.0f, 0.0f))); assert(isConsiderZero(intr.penetrationDepth - 0.5f)); } /// Checks sphere and OBB for intersection Intersection intrSphereVsOBB(ref Sphere s, ref OBB b) { Intersection intr; intr.fact = false; intr.penetrationDepth = 0.0; intr.normal = Vector3f(0.0f, 0.0f, 0.0f); intr.point = Vector3f(0.0f, 0.0f, 0.0f); Vector3f relativeCenter = s.center - b.transform.translation; relativeCenter = b.transform.invRotate(relativeCenter); if (abs(relativeCenter.x) - s.radius > b.extent.x || abs(relativeCenter.y) - s.radius > b.extent.y || abs(relativeCenter.z) - s.radius > b.extent.z) return intr; Vector3f closestPt = Vector3f(0.0f, 0.0f, 0.0f); float distance; distance = relativeCenter.x; if (distance > b.extent.x) distance = b.extent.x; if (distance < -b.extent.x) distance = -b.extent.x; closestPt.x = distance; distance = relativeCenter.y; if (distance > b.extent.y) distance = b.extent.y; if (distance < -b.extent.y) distance = -b.extent.y; closestPt.y = distance; distance = relativeCenter.z; if (distance > b.extent.z) distance = b.extent.z; if (distance < -b.extent.z) distance = -b.extent.z; closestPt.z = distance; float distanceSqr = (closestPt - relativeCenter).lengthsqr; if (distanceSqr > s.radius * s.radius) return intr; Vector3f closestPointWorld = closestPt * b.transform; intr.fact = true; intr.normal = -(closestPointWorld - s.center).normalized; intr.point = closestPointWorld; intr.penetrationDepth = s.radius - sqrt(distanceSqr); return intr; } /// unittest { Sphere sphere = Sphere(Vector3f(0, 1.9f, 0), 1.0f); OBB obb = OBB(Vector3f(0, 0, 0), Vector3f(1, 1, 1)); Intersection isec = intrSphereVsOBB(sphere, obb); assert(isec.fact); assert(isConsiderZero(isec.penetrationDepth - 0.1f)); assert(isAlmostZero(isec.point - Vector3f(0.0f, 1.0f, 0.0f))); assert(isAlmostZero(isec.normal - Vector3f(0.0f, 1.0f, 0.0f))); } ================================================ FILE: dlib/geometry/mpr.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.geometry.mpr; import dlib.math.vector; import dlib.math.matrix; import dlib.math.transformation; import dlib.math.utils; import dlib.geometry.intersection; import dlib.geometry.support; /** * Implementation of the Minkowski Portal Refinement algorithm. * Detects intersection between two arbitrary convex shapes * defined by their support functions and transformation matrices. */ Intersection intrMPR(S1, S2)( S1 s1, Matrix4x4f transform1, S2 s2, Matrix4x4f transform2) { enum float collideEpsilon = 1e-4f; enum maxIterations = 10; // Used variables Vector3f temp1; Vector3f v01, v02, v0; Vector3f v11, v12, v1; Vector3f v21, v22, v2; Vector3f v31, v32, v3; Vector3f v41, v42, v4; // Initialization of the output Intersection c; c.point = Vector3f(0.0f, 0.0f, 0.0f); c.normal = Vector3f(0.0f, 0.0f, 0.0f); c.penetrationDepth = 0.0f; c.fact = false; // Get the center of shape1 in world coordinates v01 = transform1.translation; // Get the center of shape2 in world coordinates v02 = transform2.translation; // v0 is the center of the Minkowski difference v0 = v02 - v01; // Avoid case where centers overlap - any direction is fine in this case if (v0.isAlmostZero) v0 = Vector3f(0.00001f, 0.0f, 0.0f); // v1 = support in direction of origin c.normal = -v0; v11 = supTransformed(s1, transform1, v0); v12 = supTransformed(s2, transform2, c.normal); v1 = v12 - v11; if (dot(v1, c.normal) <= 0.0f) { c.fact = false; return c; } // v2 = support perpendicular to v1,v0 c.normal = cross(v1, v0); if (c.normal.isAlmostZero) { c.normal = v1 - v0; c.normal.normalize(); c.point = v11; c.point += v12; c.point *= 0.5f; c.penetrationDepth = dot(v12 - v11, c.normal); c.fact = true; return c; } v21 = supTransformed(s1, transform1, -c.normal); v22 = supTransformed(s2, transform2, c.normal); v2 = v22 - v21; if (dot(v2, c.normal) <= 0.0f) { c.fact = false; return c; } // Determine whether origin is on + or - side of plane (v1,v0,v2) c.normal = cross(v1 - v0, v2 - v0); float dist = dot(c.normal, v0); // If the origin is on the - side of the plane, reverse the direction of the plane if (dist > 0.0f) { swap(&v1, &v2); swap(&v11, &v21); swap(&v12, &v22); c.normal = -c.normal; } int phase2 = 0; int phase1 = 0; bool hit = false; // Phase One: Identify a portal while (true) { if (phase1 > maxIterations) { c.fact = false; return c; } phase1++; // Obtain the support point in a direction perpendicular to the existing plane // Note: This point is guaranteed to lie off the plane v31 = supTransformed(s1, transform1, -c.normal); v32 = supTransformed(s2, transform2, c.normal); v3 = v32 - v31; if (dot(v3, c.normal) <= 0.0f) { c.fact = false; return c; } // If origin is outside (v1,v0,v3), then eliminate v2 and loop temp1 = cross(v1, v3); if (dot(temp1, v0) < 0.0f) { v2 = v3; v21 = v31; v22 = v32; c.normal = cross(v1 - v0, v3 - v0); continue; } // If origin is outside (v3,v0,v2), then eliminate v1 and loop temp1 = cross(v3, v2); if (dot(temp1, v0) < 0.0f) { v1 = v3; v11 = v31; v12 = v32; c.normal = cross(v3 - v0, v2 - v0); continue; } // Phase Two: Refine the portal // We are now inside of a wedge... while (true) { phase2++; // Compute normal of the wedge face c.normal = cross(v2 - v1, v3 - v1); // Can this happen? Can it be handled more cleanly? //if (c.normal.isAlmostZero) // return true; c.normal.normalize(); // Compute distance from origin to wedge face float d = dot(c.normal, v1); // If the origin is inside the wedge, we have a hit if (d >= 0 && !hit) hit = true; // Find the support point in the direction of the wedge face v41 = supTransformed(s1, transform1, -c.normal); v42 = supTransformed(s2, transform1, c.normal); v4 = v42 - v41; float delta = dot(v4 - v3, c.normal); c.penetrationDepth = dot(v4, c.normal); // If the boundary is thin enough or the origin is outside // the support plane for the newly discovered vertex, then we can terminate if (delta <= collideEpsilon || c.penetrationDepth <= 0.0f || phase2 > maxIterations) { if (hit) { float b0 = dot(cross(v1, v2), v3); float b1 = dot(cross(v3, v2), v0); float b2 = dot(cross(v0, v1), v3); float b3 = dot(cross(v2, v1), v0); float sum = b0 + b1 + b2 + b3; if (sum <= 0) { b0 = 0; b1 = dot(cross(v2, v3), c.normal); b2 = dot(cross(v3, v1), c.normal); b3 = dot(cross(v1, v2), c.normal); sum = b1 + b2 + b3; } float inv = 1.0f / sum; c.point = v01 * b0; c.point += v11 * b1; c.point += v21 * b2; c.point += v31 * b3; c.point += v02 * b0; c.point += v12 * b1; c.point += v22 * b2; c.point += v32 * b3; c.point *= inv * 0.5f; } c.fact = hit; return c; } // Compute the tetrahedron dividing face (v4,v0,v3) temp1 = cross(v4, v0); float d2 = dot(temp1, v1); if (d2 >= 0.0f) { d2 = dot(temp1, v2); if (d2 >= 0.0f) { // Inside d1 & inside d2 ==> eliminate v1 v1 = v4; v11 = v41; v12 = v42; } else { // Inside d1 & outside d2 ==> eliminate v3 v3 = v4; v31 = v41; v32 = v42; } } else { d2 = dot(temp1, v3); if (d2 >= 0.0f) { // Outside d1 & inside d3 ==> eliminate v2 v2 = v4; v21 = v41; v22 = v42; } else { // Outside d1 & outside d3 ==> eliminate v1 v1 = v4; v11 = v41; v12 = v42; } } } } // Should never get here assert(0); } ================================================ FILE: dlib/geometry/obb.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.geometry.obb; import dlib.math.vector; import dlib.math.matrix; import dlib.math.transformation; /// Oriented bounding box struct OBB { Vector3f extent; Matrix4x4f transform; this(Vector3f position, Vector3f size) { transform = Matrix4x4f.identity; center = position; extent = size; } @property { Vector3f center() { return transform.translation; } Vector3f center(Vector3f v) { transform.a14 = v.x; transform.a24 = v.y; transform.a34 = v.z; return v; } } @property Matrix3x3f orient() { return matrix4x4to3x3(transform); } } ================================================ FILE: dlib/geometry/package.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Computational geometry * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.geometry; public { import dlib.geometry.aabb; import dlib.geometry.frustum; import dlib.geometry.intersection; import dlib.geometry.mpr; import dlib.geometry.obb; import dlib.geometry.plane; import dlib.geometry.ray; import dlib.geometry.sphere; import dlib.geometry.support; import dlib.geometry.triangle; import dlib.geometry.trimesh; import dlib.geometry.utils; } ================================================ FILE: dlib/geometry/plane.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.geometry.plane; import std.math; import dlib.math.vector; import dlib.math.utils; /// Infinite plane struct Plane { /// Return a Plane with all values at zero static Plane opCall() { return Plane(0.0f, 0.0f, 0.0f, 0.0f); } /// Return a Plane with the Vec3f component of n and distance of d static Plane opCall(Vector3f n, float d) { return Plane(n.x, n.y, n.z, d); } /// Return a Plane with a Vec3f component of x, y, z and distance of d static Plane opCall(float x, float y, float z, float d) { Plane p; p.x = x; p.y = y; p.z = z; p.d = d; return p; } void fromPoints(Vector3f p0, Vector3f p1, Vector3f p2) { Vector3f v0 = p0 - p1; Vector3f v1 = p2 - p1; Vector3f n = cross(v1, v0); n.normalize(); x = n.x; y = n.y; z = n.z; d = -(p0.x * x + p0.y * y + p0.z * z); } void fromPointAndNormal(Vector3f p, Vector3f n) { n.normalize(); x = n.x; y = n.y; z = n.z; d = -(p.x * x + p.y * y + p.z * z); } float dot(Vector3f p) { return x * p.x + y * p.y + z * p.z; } void normalize() { float len = sqrt(x * x + y * y + z * z); x /= len; y /= len; z /= len; d /= len; } Plane normalized() { Plane res; float len = sqrt(x * x + y * y + z * z); return Plane(x / len, y / len, z / len, d / len); } /** * Get the distance from the center of the plane to the given point. * This is useful for determining which side of the plane the point is on. */ float distance(Vector3f p) { return x * p.x + y * p.y + z * p.z + d; } Vector3f reflect(Vector3f vec) { float d = distance(vec); return vec + Vector3f(-x, -y, -z) * 2 * d; } Vector3f project(Vector3f p) { float h = distance(p); return Vector3f(p.x - x * h, p.y - y * h, p.z - z * h); } bool isOnPlane(Vector3f p, float threshold = 0.001f) { float d = distance(p); if (d < threshold && d > -threshold) return true; return false; } /** * Calculate the intersection between this plane and a line * If the plane and the line are parallel, false is returned */ bool intersectsLine(Vector3f p0, Vector3f p1, ref float t) { Vector3f dir = p1 - p0; float div = dot(dir); if (div == 0.0) return false; t = -distance(p0) / div; return true; } bool intersectsLine(Vector3f p0, Vector3f p1, ref Vector3f ip) { Vector3f dir = p1 - p0; float div = dot(dir); if (div == 0.0) { ip = (p0 + p1) * 0.5f; return false; } float u = -distance(p0) / div; ip = p0 + (p1 - p0) * u; return true; } bool intersectsLineSegment(Vector3f p0, Vector3f p1, ref Vector3f ip) { Vector3f ray = p1 - p0; // calculate plane float d = dot(position); float dr = dot(ray); if (abs(dr) < EPSILON) return false; // avoid divide by zero // Compute the t value for the directed line ray intersecting the plane float t = (d - dot(p0)) / dr; // scale the ray by t Vector3f newRay = ray * t; // calc contact point ip = p0 + newRay; if (t >= 0.0 && t <= 1.0) return true; // line intersects plane return false; // line does not } float opIndex(size_t i) { return arrayof[i]; } float opIndexAssign(float value, size_t i) { return (arrayof[i] = value); } union { float[4] arrayof; Vector4f vectorof; struct { float a, b, c, d; } Vector3f normal; } alias vectorof this; @property Vector3f position() { return -(normal * d); } } /// unittest { Plane plane = Plane(Vector3f(0, 1, 0), 0.0f); assert(isConsiderZero(plane.distance(Vector3f(0, -1, 0)) + 1.0f)); float d = plane.dot(Vector3f(1, 0, 0)); assert(isConsiderZero(d)); assert(isAlmostZero(plane.position)); assert(plane.isOnPlane(Vector3f(0, 0, 0))); float t; assert(plane.intersectsLine(Vector3f(0, -1, 0), Vector3f(0, 1, 0), t)); assert(isConsiderZero(t - 0.5f)); Vector3f ip; assert(plane.intersectsLineSegment(Vector3f(0, -1, 0), Vector3f(0, 1, 0), ip)); } ================================================ FILE: dlib/geometry/ray.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.geometry.ray; import std.math; import dlib.math.vector; import dlib.math.utils; import dlib.geometry.sphere; import dlib.geometry.triangle; /// Ray with starting and ending points struct Ray { Vector3f p0; Vector3f p1; float t; this(Vector3f begin, Vector3f end) { p0 = begin; p1 = end; } bool intersectSphere(Sphere sphere, out Vector3f intersectionPoint) { Vector3f dir = p1 - p0; dir.normalize(); Vector3f dist = sphere.center - p0; float B = dot(dist,dir); float D = sphere.radius * sphere.radius - dot(dist,dist) + B * B; if (D < 0.0f) { intersectionPoint = Vector3f(0.0f, 0.0f, 0.0f); return false; } float t0 = B - sqrt(D); float t1 = B + sqrt(D); if (t0 > 0.0f) { t = t0; intersectionPoint = p0 + dir * t0; return true; } if (t1 > 0.0f) { t = t1; intersectionPoint = p0 + dir * t1; return true; } intersectionPoint = Vector3f(0.0f, 0.0f, 0.0f); return false; } bool intersectTriangle(Triangle tri, out Vector3f intersectionPoint) { Vector3f u, v, n; // triangle vectors Vector3f dir, w0, w; // ray vectors float r, a, b; // params to calc ray-plane intersect // get triangle edge vectors and plane normal u = tri.v[1] - tri.v[0]; v = tri.v[2] - tri.v[0]; n = cross(u, v); // cross product if (n.isZero) // triangle is degenerate { intersectionPoint = Vector3f(0.0f, 0.0f, 0.0f); return false; } dir = p1 - p0; // ray direction vector w0 = p0 - tri.v[0]; a = -dot(n, w0); b = dot(n, dir); if (fabs(b) < EPSILON) // ray is parallel to triangle plane { // no intersect intersectionPoint = Vector3f(0.0f, 0.0f, 0.0f); return false; } // get intersect point of ray with triangle plane r = a / b; if (r < 0.0f) // ray goes away from triangle { // no intersect intersectionPoint = Vector3f(0.0f, 0.0f, 0.0f); return false; } Vector3f I = p0 + dir * r; // intersect point of ray and plane float uu, uv, vv, wu, wv, D; uu = dot(u, u); uv = dot(u, v); vv = dot(v, v); w = I - tri.v[0]; wu = dot(w, u); wv = dot(w, v); D = uv * uv - uu * vv; // get and test parametric coords float s, t; s = (uv * wv - vv * wu) / D; if (s < 0.0 || s > 1.0) // point is outside of the triangle { intersectionPoint = Vector3f(0.0f, 0.0f, 0.0f); return false; } t = (uv * wu - uu * wv) / D; if (t < 0.0 || (s + t) > 1.0) // point is outside of the triangle { intersectionPoint = Vector3f(0.0f, 0.0f, 0.0f); return false; } intersectionPoint = I; // point is inside of the triangle return true; } } /// unittest { Ray r = Ray(Vector3f(0, 0, 0), Vector3f(10, 0, 0)); Sphere s = Sphere(Vector3f(3, 0, 0), 1); Vector3f intersectionPoint; assert(r.intersectSphere(s, intersectionPoint)); assert(isAlmostZero(intersectionPoint - Vector3f(2, 0, 0))); Triangle tri; tri.v[0] = Vector3f(5, -1, 1); tri.v[1] = Vector3f(5, -1, -1); tri.v[2] = Vector3f(5, 1, 0); assert(r.intersectTriangle(tri, intersectionPoint)); assert(isAlmostZero(intersectionPoint - Vector3f(5, 0, 0))); } ================================================ FILE: dlib/geometry/sphere.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.geometry.sphere; import dlib.math.vector; /// Sphere object struct Sphere { Vector3f center; float radius; this(Vector3f newCenter, float newRadius) { center = newCenter; radius = newRadius; } bool containsPoint(Vector3f pt) { float dist = (pt - center).lengthsqr; return dist < (radius * radius) ? true : false; } } /// unittest { Sphere sphere = Sphere(Vector3f(0, 0, 0), 1.0f); assert(sphere.containsPoint(Vector3f(0.1f, 0, 0))); } ================================================ FILE: dlib/geometry/support.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.geometry.support; import std.math; import dlib.math.vector; import dlib.math.matrix; import dlib.math.utils; /// Sphere support function Vector3f supSphere(Vector3f dir, float radius) { return dir * radius; } /// Ellipsoid support function Vector3f supEllipsoid(Vector3f dir, Vector3f radii) { return dir * radii; } /// AABB support function Vector3f subBox(Vector3f dir, Vector3f halfSize) { Vector3f result; result.x = sign(dir.x) * halfSize.x; result.y = sign(dir.y) * halfSize.y; result.z = sign(dir.z) * halfSize.z; return result; } /// Cylinder support function Vector3f supCylinder(Vector3f dir, float radius, float height) { Vector3f result; float sigma = sqrt((dir.x * dir.x + dir.z * dir.z)); if (sigma > 0.0f) { result.x = dir.x / sigma * radius; result.y = sign(dir.y) * height * 0.5f; result.z = dir.z / sigma * radius; } else { result.x = 0.0f; result.y = sign(dir.y) * height * 0.5f; result.z = 0.0f; } return result; } /// Cone support function Vector3f supCone(Vector3f dir, float radius, float height) { float zdist = dir[0] * dir[0] + dir[1] * dir[1]; float len = zdist + dir[2] * dir[2]; zdist = sqrt(zdist); len = sqrt(len); float half_h = height * 0.5; float sin_a = radius / sqrt(radius * radius + 4.0f * half_h * half_h); if (dir[2] > len * sin_a) return Vector3f(0.0f, 0.0f, half_h); else if (zdist > 0.0f) { float rad = radius / zdist; return Vector3f(rad * dir[0], rad * dir[1], -half_h); } else return Vector3f(0.0f, 0.0f, -half_h); } /// Capsule support function Vector3f supCapsule(Vector3f dir, float radius, float height) { float half_h = height * 0.5f; Vector3f pos1 = Vector3f(0.0f, 0.0f, half_h); Vector3f pos2 = Vector3f(0.0f, 0.0f, -half_h); Vector3f v = dir * radius; pos1 += v; pos2 += v; if (dir.dot(pos1) > dir.dot(pos2)) return pos1; else return pos2; } /// Convex hull support function Vector3f supConvexHull(Vector3f dir, Vector3f[] vertices) { float maxdot = -float.max; Vector3f bestv; foreach(v; vertices) { float d = dir.dot(v); if (d > maxdot) { bestv = v; maxdot = d; } } return bestv; } /// Triangle support function Vector3f supTriangle(Vector3f dir, Vector3f[3] v) { float dota = dir.dot(v[0]); float dotb = dir.dot(v[1]); float dotc = dir.dot(v[2]); if (dota > dotb) { if (dotc > dota) return v[2]; else return v[0]; } else { if (dotc > dotb) return v[2]; else return v[1]; } } /// Transformed support function Vector3f supTransformed(S)(S shape, Matrix4x4f transformation, Vector3f dir) { Vector3f result; result.x = ((dir.x * m.a11) + (dir.y * m.a21)) + (dir.z * m.a31); result.y = ((dir.x * m.a12) + (dir.y * m.a22)) + (dir.z * m.a32); result.z = ((dir.x * m.a13) + (dir.y * m.a23)) + (dir.z * m.a33); result = shape.support(result); float x = ((result.x * m.a11) + (result.y * m.a12)) + (result.z * m.a13); float y = ((result.x * m.a21) + (result.y * m.a22)) + (result.z * m.a23); float z = ((result.x * m.a31) + (result.y * m.a32)) + (result.z * m.a33); result.x = m.a14 + x; result.y = m.a24 + y; result.z = m.a34 + z; return Vector3f; } ================================================ FILE: dlib/geometry/triangle.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.geometry.triangle; private { import std.math; import dlib.math.vector; import dlib.geometry.aabb; } /// Triangle object struct Triangle { Vector3f[3] v; Vector3f[3] n; Vector2f[3] t1; Vector2f[3] t2; Vector4f[3] tg; Vector3f[3] edges; Vector3f normal; Vector3f barycenter; float d; int materialIndex; /// Returns -1 if given point is inside the triangle int isPointInside(Vector3f point) { //select coordinate int dim0, dim1, plane; float clockness; // 1.0 counter clockwise, -1.0 clockwise if (abs(normal[1]) > abs(normal[2])) { if (abs(normal[1]) > abs(normal[0])) //use y plane { plane = 1; dim0 = 2; dim1 = 0; } else //use x plane { plane = 0; dim0 = 1; dim1 = 2; } } else if (abs(normal[2]) > abs(normal[0])) //use z plane { plane = 2; dim0 = 0; dim1 = 1; } else //use x plane { plane = 0; dim0 = 1; dim1 = 2; } clockness = (normal[plane] > 0.0f)? 1.0f : -1.0f; float det0, det1, det2; det0 = (point[dim0] - v[0][dim0]) * (v[0][dim1] - v[1][dim1]) + (v[0][dim1] - point[dim1]) * (v[0][dim0] - v[1][dim0]); det1 = (point[dim0] - v[1][dim0]) * (v[1][dim1] - v[2][dim1]) + (v[1][dim1] - point[dim1]) * (v[1][dim0] - v[2][dim0]); det2 = (point[dim0] - v[2][dim0]) * (v[2][dim1] - v[0][dim1]) + (v[2][dim1] - point[dim1]) * (v[2][dim0] - v[0][dim0]); int ret; if (det0 > 0.0f) { if (det1 > 0.0f) { if (det2 > 0.0f) ret = -1; // inside else ret = 5; // outside edge 2 } else { if (det2 > 0.0f) ret = 3; // outside edge 1 else ret = 4; // outside vertex 2 } } else { if (det1 > 0.0f) { if (det2 > 0.0f) ret = 1; // outside edge 0 else ret = 0; // outside vertex 0 } else { if (det2 > 0.0f) ret = 2; // outside vertex 1 else ret = -1; // inside } } if (ret == -1) return ret; if (clockness == -1.0f) ret = (ret + 3) % 6; return ret; } AABB boundingBox() { Vector3f pmin = v[0]; Vector3f pmax = pmin; void adjustMinPoint(Vector3f p) { if (p.x < pmin.x) pmin.x = p.x; if (p.y < pmin.y) pmin.y = p.y; if (p.z < pmin.z) pmin.z = p.z; } void adjustMaxPoint(Vector3f p) { if (p.x > pmax.x) pmax.x = p.x; if (p.y > pmax.y) pmax.y = p.y; if (p.z > pmax.z) pmax.z = p.z; } foreach(vertex; v) { adjustMinPoint(vertex); adjustMaxPoint(vertex); } return boxFromMinMaxPoints(pmin, pmax); } } /// unittest { Triangle tri = { v: [Vector3f(0, 0, 0), Vector3f(0, 1, 0), Vector3f(1, 0, 0)], n: [Vector3f(0, 0, 1), Vector3f(0, 0, 1), Vector3f(0, 0, 1)], normal: Vector3f(0, 0, 1) }; assert(tri.isPointInside(Vector3f(0.5f, 0.5f, 0.0f)) == -1); assert(tri.isPointInside(Vector3f(-0.5f, 0.5f, 0.0f)) != -1); AABB aabb = tri.boundingBox(); assert(aabb.center == Vector3f(0.5f, 0.5f, 0.0f)); assert(aabb.size == Vector3f(0.5f, 0.5f, 0.0f)); } ================================================ FILE: dlib/geometry/trimesh.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.geometry.trimesh; import std.stdio; import std.math; import dlib.core.memory; import dlib.container.array; import dlib.math.vector; import dlib.geometry.triangle; struct Index { uint a, b, c; } struct FaceGroup { Array!Index indicesArray; int materialIndex; @property Index[] indices() { return indicesArray.data; } void addFace(uint a, uint b, uint c) { indicesArray.insertBack(Index(a, b, c)); } void addFaces(Index[] inds) { indicesArray.insertBack(inds); } } /// Triangle mesh class TriMesh { Array!(Vector3f) verticesArray; Array!(Vector3f) normalsArray; Array!(Vector3f) tangentsArray; Array!(Vector2f) texcoordsArray; Array!FaceGroup facegroupsArray; @property Vector3f[] vertices() { return verticesArray.data; } @property Vector3f[] normals() { return normalsArray.data; } @property Vector3f[] tangents() { return tangentsArray.data; } @property Vector2f[] texcoords() { return texcoordsArray.data; } @property FaceGroup[] facegroups() { return facegroupsArray.data; } this() { } ~this() { verticesArray.free(); normalsArray.free(); tangentsArray.free(); texcoordsArray.free(); foreach(fc; facegroupsArray) fc.indicesArray.free(); facegroupsArray.free(); } void addVertex(Vector3f v) { verticesArray.insertBack(v); } void addNormal(Vector3f n) { normalsArray.insertBack(n); } void addTangent(Vector3f t) { tangentsArray.insertBack(t); } void addTexcoord(Vector2f t) { texcoordsArray.insertBack(t); } void addVertices(Vector3f[] verts) { verticesArray.insertBack(verts); } void addNormals(Vector3f[] norms) { normalsArray.insertBack(norms); } void addTangents(Vector3f[] tans) { tangentsArray.insertBack(tans); } void addTexcoords(Vector2f[] texs) { texcoordsArray.insertBack(texs); } FaceGroup* addFacegroup() { FaceGroup fg; facegroupsArray.insertBack(fg); return &facegroupsArray.data[$-1]; } Triangle getTriangle(uint facegroupIndex, uint triIndex) { Triangle tri; Index triIdx = facegroupsArray[facegroupIndex].indicesArray[triIndex]; tri.v[0] = verticesArray[triIdx.a]; tri.v[1] = verticesArray[triIdx.b]; tri.v[2] = verticesArray[triIdx.c]; tri.n[0] = normalsArray[triIdx.a]; tri.n[1] = normalsArray[triIdx.b]; tri.n[2] = normalsArray[triIdx.c]; if (texcoordsArray.length == verticesArray.length) { tri.t1[0] = texcoordsArray[triIdx.a]; tri.t1[1] = texcoordsArray[triIdx.b]; tri.t1[2] = texcoordsArray[triIdx.c]; } tri.normal = planeNormal(tri.v[0], tri.v[1], tri.v[2]); tri.barycenter = (tri.v[0] + tri.v[1] + tri.v[2]) / 3; tri.d = (tri.v[0].x * tri.normal.x + tri.v[0].y * tri.normal.y + tri.v[0].z * tri.normal.z); tri.edges[0] = tri.v[1] - tri.v[0]; tri.edges[1] = tri.v[2] - tri.v[1]; tri.edges[2] = tri.v[0] - tri.v[2]; tri.materialIndex = facegroupsArray[facegroupIndex].materialIndex; return tri; } /** * Read-only triangle aggregate */ int opApply(scope int delegate(ref Triangle) dg) { int result = 0; for (uint fgi = 0; fgi < facegroupsArray.length; fgi++) for (uint i = 0; i < facegroupsArray[fgi].indices.length; i++) { Triangle tri = getTriangle(fgi, i); result = dg(tri); if (result) break; } return result; } void genTangents() { tangentsArray.free(); Vector3f[] sTan = New!(Vector3f[])(verticesArray.length); Vector3f[] tTan = New!(Vector3f[])(verticesArray.length); foreach(i, v; sTan) { sTan[i] = Vector3f(0.0f, 0.0f, 0.0f); tTan[i] = Vector3f(0.0f, 0.0f, 0.0f); } foreach(ref fg; facegroupsArray) foreach(ref index; fg.indicesArray) { uint i0 = index.a; uint i1 = index.b; uint i2 = index.c; Vector3f v0 = verticesArray[i0]; Vector3f v1 = verticesArray[i1]; Vector3f v2 = verticesArray[i2]; Vector2f w0 = texcoordsArray[i0]; Vector2f w1 = texcoordsArray[i1]; Vector2f w2 = texcoordsArray[i2]; float x1 = v1.x - v0.x; float x2 = v2.x - v0.x; float y1 = v1.y - v0.y; float y2 = v2.y - v0.y; float z1 = v1.z - v0.z; float z2 = v2.z - v0.z; float s1 = w1[0] - w0[0]; float s2 = w2[0] - w0[0]; float t1 = w1[1] - w0[1]; float t2 = w2[1] - w0[1]; float r = (s1 * t2) - (s2 * t1); // Prevent division by zero if (r == 0.0f) r = 1.0f; float oneOverR = 1.0f / r; Vector3f sDir = Vector3f((t2 * x1 - t1 * x2) * oneOverR, (t2 * y1 - t1 * y2) * oneOverR, (t2 * z1 - t1 * z2) * oneOverR); Vector3f tDir = Vector3f((s1 * x2 - s2 * x1) * oneOverR, (s1 * y2 - s2 * y1) * oneOverR, (s1 * z2 - s2 * z1) * oneOverR); sTan[i0] += sDir; tTan[i0] += tDir; sTan[i1] += sDir; tTan[i1] += tDir; sTan[i2] += sDir; tTan[i2] += tDir; } tangentsArray.resize(vertices.length, Vector3f(0.0f, 0.0f, 0.0f)); // Calculate vertex tangent foreach(i, v; tangents) { Vector3f n = normalsArray[i]; Vector3f t = sTan[i]; // Gram-Schmidt orthogonalize Vector3f tangent = (t - n * dot(n, t)); tangent.normalize(); tangentsArray[i] = tangent; } Delete(sTan); Delete(tTan); } } ================================================ FILE: dlib/geometry/utils.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.geometry.utils; import dlib.math.vector; Vector3f triBarycentricCoords(Vector3f v0, Vector3f v1, Vector3f v2, Vector3f p) { float triArea = cross((v1 - v0), (v2 - v0)).length * 0.5f; float u = (cross((v1 - p ), (v2 - p)).length * 0.5f) / triArea; float v = (cross((v0 - p ), (v2 - p)).length * 0.5f) / triArea; float w = (cross((v0 - p ), (v1 - p)).length * 0.5f) / triArea; return Vector3f(u, v, w); } // ta, tb, tc - triangle texture coords // s, t - coords in texture space Vector3f triBarycentricCoords(Vector2f ta, Vector2f tb, Vector2f tc, float s, float t) { 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); float m1 = ((tb.x * tc.y) - (tb.y * tc.x) - (s * tc.y) + (t * tc.x) + (s * tb.y) - (t * tb.x)) / d; float m2 = ((s * tc.y) - (t * tc.x) - (ta.x * tc.y) + (ta.y * tc.x) + (ta.x * t) - (ta.y * s)) / d; float m3 = ((tb.x * t) - (tb.y * s) - (ta.x * t) + (ta.y * s) + (ta.x * tb.y) - (ta.y * tb.x)) / d; return Vector3f(m1, m2, m3); } // va, vb, vc - triangle vectors (vertices, normals, colors, etc) // bcc - barycentric coords Vector3f triTextureSpaceToObjectSpace(Vector3f va, Vector3f vb, Vector3f vc, Vector3f bcc) { return Vector3f( va.x * bcc.x + vb.x * bcc.y + vc.x * bcc.z, va.y * bcc.x + vb.y * bcc.y + vc.y * bcc.z, va.z * bcc.x + vb.z * bcc.y + vc.z * bcc.z, ); } Vector2f triObjectSpaceToTextureSpace( Vector3f p1, Vector3f p2, Vector3f p3, Vector2f uv1, Vector2f uv2, Vector2f uv3, Vector3f pos) { // Compute vectors Vector3f v0 = p3 - p1; Vector3f v1 = p2 - p1; Vector3f v2 = pos - p1; // Compute dot products float dot00 = dot(v0, v0); float dot01 = dot(v0, v1); float dot02 = dot(v0, v2); float dot11 = dot(v1, v1); float dot12 = dot(v1, v2); // Compute barycentric coordinates float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01); float u = (dot11 * dot02 - dot01 * dot12) * invDenom; float v = (dot00 * dot12 - dot01 * dot02) * invDenom; Vector2f t2 = uv2 - uv1; Vector2f t1 = uv3 - uv1; return uv1 + t1*u + t2*v; } float sign(Vector2f p1, Vector2f p2, Vector2f p3) { return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); } bool isPointInTriangle2D(Vector2f pt, Vector2f v1, Vector2f v2, Vector2f v3) { bool b1, b2, b3; b1 = sign(pt, v1, v2) < 0.0f; b2 = sign(pt, v2, v3) < 0.0f; b3 = sign(pt, v3, v1) < 0.0f; return ((b1 == b2) && (b2 == b3)); } ================================================ FILE: dlib/image/animation.d ================================================ /* Copyright (c) 2017-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Animated images * * Copyright: Timur Gafarov 2017-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.animation; import dlib.core.memory; import dlib.image.image; /** * Animated image interface */ interface SuperAnimatedImage: SuperImage { @property size_t frameSize(); @property uint numFrames(); @property uint currentFrame(); @property void currentFrame(uint f); uint advanceFrame(); } /** * Extension of standard Image that handles animation * * Description: * AnimatedImage can store more than one frame of pixel data. * Current frame can be switched with currentFrame property. * All usual image operations work on current frame, so * you can use this class with any existing dlib.image * functionality: save to file, apply filters, etc. */ class AnimatedImage(IntegerPixelFormat fmt): Image!(fmt), SuperAnimatedImage { protected: uint _numFrames; uint _currentFrame = 0; size_t _frameSize; public: this(uint w, uint h, uint frames) { _numFrames = frames; super(w, h); _frameSize = _width * _height * _pixelSize; } override @property SuperImage dup() { auto res = new AnimatedImage!(fmt)(_width, _height, _numFrames); res.data[] = data[]; return res; } override SuperImage createSameFormat(uint w, uint h) { return new AnimatedImage!(fmt)(w, h, _numFrames); } @property size_t frameSize() { return _frameSize; } @property uint numFrames() { return _numFrames; } @property uint currentFrame() { return _currentFrame; } @property void currentFrame(uint f) { if (f >= _numFrames) _currentFrame = _numFrames - 1; else _currentFrame = f; } uint advanceFrame() { currentFrame = currentFrame + 1; return _currentFrame; } override @property ubyte[] data() { if (_currentFrame >= _numFrames) _currentFrame = _numFrames - 1; size_t offset = _frameSize * _currentFrame; return _data[offset..offset+_frameSize]; } protected override void allocateData() { _data = new ubyte[_width * _height * _pixelSize * _numFrames]; } } /// Specialization of AnimatedImage for 8-bit luminance pixel format alias AnimatedImageL8 = AnimatedImage!(IntegerPixelFormat.L8); /// Specialization of AnimatedImage for 8-bit luminance-alpha pixel format alias AnimatedImageLA8 = AnimatedImage!(IntegerPixelFormat.LA8); /// Specialization of AnimatedImage for 8-bit RGB pixel format alias AnimatedImageRGB8 = AnimatedImage!(IntegerPixelFormat.RGB8); /// Specialization of AnimatedImage for 8-bit RGBA pixel format alias AnimatedImageRGBA8 = AnimatedImage!(IntegerPixelFormat.RGBA8); /// Specialization of AnimatedImage for 16-bit luminance pixel format alias AnimatedImageL16 = AnimatedImage!(IntegerPixelFormat.L16); /// Specialization of AnimatedImage for 16-bit luminance-alpha pixel format alias AnimatedImageLA16 = AnimatedImage!(IntegerPixelFormat.LA16); /// Specialization of AnimatedImage for 16-bit RGB pixel format alias AnimatedImageRGB16 = AnimatedImage!(IntegerPixelFormat.RGB16); /// Specialization of AnimatedImage for 16-bit RGBA pixel format alias AnimatedImageRGBA16 = AnimatedImage!(IntegerPixelFormat.RGBA16); /** * Factory class for animated images */ class AnimatedImageFactory: SuperImageFactory { SuperImage createImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1) { return animatedImage(w, h, channels, bitDepth, numFrames); } } private AnimatedImageFactory _defaultAnimatedImageFactory; /** * Get default AnimatedImageFactory singleton */ AnimatedImageFactory animatedImageFactory() { if (!_defaultAnimatedImageFactory) _defaultAnimatedImageFactory = new AnimatedImageFactory(); return _defaultAnimatedImageFactory; } /** * Factory function for animated images */ SuperImage animatedImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1) in { assert(channels > 0 && channels <= 4); assert(bitDepth == 8 || bitDepth == 16); } do { switch(channels) { case 1: { if (bitDepth == 8) return new AnimatedImageL8(w, h, numFrames); else return new AnimatedImageL16(w, h, numFrames); } case 2: { if (bitDepth == 8) return new AnimatedImageLA8(w, h, numFrames); else return new AnimatedImageLA16(w, h, numFrames); } case 3: { if (bitDepth == 8) return new AnimatedImageRGB8(w, h, numFrames); else return new AnimatedImageRGB16(w, h, numFrames); } case 4: { if (bitDepth == 8) return new AnimatedImageRGBA8(w, h, numFrames); else return new AnimatedImageRGBA16(w, h, numFrames); } default: assert(0); } } /** * AnimatedImage that uses dlib.core.memory instead of GC */ class UnmanagedAnimatedImage(IntegerPixelFormat fmt): AnimatedImage!(fmt) { override @property SuperImage dup() { auto res = New!(UnmanagedAnimatedImage!(fmt))(_width, _height, _numFrames); res.data[] = data[]; return res; } override SuperImage createSameFormat(uint w, uint h) { return New!(UnmanagedAnimatedImage!(fmt))(w, h, _numFrames); } this(uint w, uint h, uint frames) { super(w, h, frames); } ~this() { Delete(_data); } protected override void allocateData() { _data = New!(ubyte[])(_width * _height * _pixelSize * _numFrames); } override void free() { Delete(this); } } /// Specialization of UnmanagedAnimatedImage for 8-bit luminance pixel format alias UnmanagedAnimatedImageL8 = UnmanagedAnimatedImage!(IntegerPixelFormat.L8); /// Specialization of UnmanagedAnimatedImage for 8-bit luminance-alpha pixel format alias UnmanagedAnimatedImageLA8 = UnmanagedAnimatedImage!(IntegerPixelFormat.LA8); /// Specialization of UnmanagedAnimatedImage for 8-bit RGB pixel format alias UnmanagedAnimatedImageRGB8 = UnmanagedAnimatedImage!(IntegerPixelFormat.RGB8); /// Specialization of UnmanagedAnimatedImage for 8-bit RGBA pixel format alias UnmanagedAnimatedImageRGBA8 = UnmanagedAnimatedImage!(IntegerPixelFormat.RGBA8); /// Specialization of UnmanagedAnimatedImage for 16-bit luminance pixel format alias UnmanagedAnimatedImageL16 = UnmanagedAnimatedImage!(IntegerPixelFormat.L16); /// Specialization of UnmanagedAnimatedImage for 16-bit luminance-alpha pixel format alias UnmanagedAnimatedImageLA16 = UnmanagedAnimatedImage!(IntegerPixelFormat.LA16); /// Specialization of UnmanagedAnimatedImage for 16-bit RGB pixel format alias UnmanagedAnimatedImageRGB16 = UnmanagedAnimatedImage!(IntegerPixelFormat.RGB16); /// Specialization of UnmanagedAnimatedImage for 16-bit RGBA pixel format alias UnmanagedAnimatedImageRGBA16 = UnmanagedAnimatedImage!(IntegerPixelFormat.RGBA16); /** * Factory class for UnmanagedAnimatedImage */ class UnmanagedAnimatedImageFactory: SuperImageFactory { SuperImage createImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1) { return unmanagedAnimatedImage(w, h, channels, bitDepth, numFrames); } } /** * Factory function for UnmanagedAnimatedImage */ SuperImage unmanagedAnimatedImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1) in { assert(channels > 0 && channels <= 4); assert(bitDepth == 8 || bitDepth == 16); } do { switch(channels) { case 1: { if (bitDepth == 8) return New!UnmanagedAnimatedImageL8(w, h, numFrames); else return New!UnmanagedAnimatedImageL16(w, h, numFrames); } case 2: { if (bitDepth == 8) return New!UnmanagedAnimatedImageLA8(w, h, numFrames); else return New!UnmanagedAnimatedImageLA16(w, h, numFrames); } case 3: { if (bitDepth == 8) return New!UnmanagedAnimatedImageRGB8(w, h, numFrames); else return New!UnmanagedAnimatedImageRGB16(w, h, numFrames); } case 4: { if (bitDepth == 8) return New!UnmanagedAnimatedImageRGBA8(w, h, numFrames); else return New!UnmanagedAnimatedImageRGBA16(w, h, numFrames); } default: assert(0); } } ================================================ FILE: dlib/image/arithmetics.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Per-pixel image arithmetics * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.arithmetics; import dlib.image.image; import dlib.image.color; /// Add two images SuperImage add(SuperImage a, SuperImage b, SuperImage outp, float t = 1.0f) in { assert(a.width == b.width); assert(a.height == b.height); } do { SuperImage img; if (outp) img = outp; else img = a.dup; foreach(y; 0..img.height) foreach(x; 0..img.width) { Color4f acol = Color4f(a[x, y]); Color4f bcol = Color4f(b[x, y]); Color4f col = acol + (bcol * t); col.a = acol.a; img[x, y] = col; } return img; } /// ditto SuperImage add(SuperImage a, SuperImage b, float t = 1.0f) { return add(a, b, null, t); } /// Subtract image b from image a SuperImage subtract(SuperImage a, SuperImage b, SuperImage outp, float t = 1.0f) in { assert(a.width == b.width); assert(a.height == b.height); } do { SuperImage img; if (outp) img = outp; else img = a.dup; foreach(y; 0..img.height) foreach(x; 0..img.width) { Color4f acol = Color4f(a[x, y]); Color4f bcol = Color4f(b[x, y]); Color4f col = acol - (bcol * t); col.a = acol.a; img[x, y] = col; } return img; } /// ditto SuperImage subtract(SuperImage a, SuperImage b, float t = 1.0f) { return subtract(a, b, null, t); } /// Multiply two images SuperImage multiply(SuperImage a, SuperImage b, SuperImage outp, float t = 1.0f) in { assert(a.width == b.width); assert(a.height == b.height); } do { SuperImage img; if (outp) img = outp; else img = a.dup; foreach(y; 0..img.height) foreach(x; 0..img.width) { Color4f acol = Color4f(a[x, y]); Color4f bcol = Color4f(b[x, y]); Color4f col = acol * (bcol * t); col.a = acol.a; img[x, y] = col; } return img; } /// ditto SuperImage multiply(SuperImage a, SuperImage b, float t = 1.0f) { return multiply(a, b, null, t); } /// Divide two images SuperImage divide(SuperImage a, SuperImage b, SuperImage outp, float t = 1.0f) in { assert(a.width == b.width); assert(a.height == b.height); } do { SuperImage img; if (outp) img = outp; else img = a.dup; foreach(y; 0..img.height) foreach(x; 0..img.width) { Color4f acol = Color4f(a[x, y]); Color4f bcol = Color4f(b[x, y]); Color4f col = acol / (bcol * t); col.a = acol.a; img[x, y] = col; } return img; } /// ditto SuperImage divide(SuperImage a, SuperImage b, float t = 1.0f) { return divide(a, b, null, t); } /// Invert image SuperImage invert(SuperImage a, SuperImage outp) { SuperImage img; if (outp) img = outp; else img = a.dup; foreach(y; 0..img.height) foreach(x; 0..img.width) { img[x, y] = a[x, y].inverse; } return img; } /// ditto SuperImage invert(SuperImage a) { return invert(a, null); } ================================================ FILE: dlib/image/canvas.d ================================================ /* Copyright (c) 2018-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Simple 2D rendering engine * * Copyright: Timur Gafarov 2018-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.canvas; import std.math; import dlib.math.vector; import dlib.math.matrix; import dlib.math.transformation; import dlib.math.utils; import dlib.math.interpolation.bezier; import dlib.container.array; import dlib.image.color; import dlib.image.image; import dlib.image.render.shapes; import dlib.core.memory; /// General options for drawing things struct CanvasState { Matrix3x3f transformation; Color4f lineColor; Color4f fillColor; float lineWidth; } /// Type of a path segment enum SegmentType { Line, BezierCubic } /// Path segment struct ContourSegment { Vector2f p1; Vector2f p2; Vector2f p3; Vector2f p4; float radius; SegmentType type; } /** * A simple 2D vector engine inspired by HTML5 canvas. * Supports rendering arbitrary polygons and cubic Bezier paths, filled and outlined. * Not real-time. */ class Canvas { protected: SuperImage _image; SuperImage tmpBuffer; // TODO: state stack CanvasState state; Array!ContourSegment contour; Vector2f penPosition; float tesselationStep = 1.0f / 40.0f; uint subpixelResolution = 4; public: this(SuperImage img) { _image = img; tmpBuffer = image.createSameFormat(_image.width, _image.height); state.transformation = Matrix3x3f.identity; state.lineColor = Color4f(0.0f, 0.0f, 0.0f, 1.0f); state.fillColor = Color4f(0.0f, 0.0f, 0.0f, 1.0f); state.lineWidth = 1.0f; penPosition = Vector2f(0.0f, 0.0f); } ~this() { contour.free(); tmpBuffer.free(); } public: SuperImage image() @property { return _image; } void fillColor(Color4f c) @property { state.fillColor = c; } Color4f fillColor() @property { return state.fillColor; } void lineColor(Color4f c) @property { state.lineColor = c; } Color4f lineColor() @property { return state.lineColor; } void lineWidth(float w) @property { state.lineWidth = w; } float lineWidth() @property { return state.lineWidth; } void resetTransform() { state.transformation = Matrix3x3f.identity; } void transform(Matrix3x3f m) { state.transformation *= m; } void translate(float x, float y) { state.transformation *= translationMatrix2D(Vector2f(x, y)); } void rotate(float a) { state.transformation *= rotationMatrix2D(a); } void scale(float x, float y) { state.transformation *= scaleMatrix2D(Vector2f(x, y)); } void clear(Color4f c) { dlib.image.render.shapes.fillColor(_image, c); } void beginPath() { penPosition = Vector2f(0.0f, 0.0f); } void endPath() { contour.free(); } void pathMoveTo(float x, float y) { penPosition = Vector2f(x, y); } void pathLineTo(float x, float y) { Vector2f p1 = penPosition; Vector2f p2 = Vector2f(x, y); pathAddLine(p1, p2); penPosition = p2; } void pathBezierTo(Vector2f cp1, Vector2f cp2, Vector2f endPoint) { pathAddBezierCubic(penPosition, cp1, cp2, endPoint); penPosition = endPoint; } void pathStroke() { dlib.image.render.shapes.fillColor(tmpBuffer, Color4f(0, 0, 0, 0)); drawContour(); blitTmpBuffer(state.lineColor); } void pathFill() { fillShape(); } protected: void pathAddLine(Vector2f p1, Vector2f p2) { ContourSegment segment; segment.p1 = p1; segment.p2 = p2; segment.type = SegmentType.Line; contour.append(segment); } void pathAddBezierCubic(Vector2f p1, Vector2f p2, Vector2f p3, Vector2f p4) { ContourSegment segment; segment.p1 = p1; segment.p2 = p2; segment.p3 = p3; segment.p4 = p4; segment.type = SegmentType.BezierCubic; contour.append(segment); } void fillShape() { Array!Vector2f poly; Array!size_t polyBounds; Vector2f startP, endP, wayP1, wayP2; foreach(ref p; contour.data) { startP = p.p1.affineTransform2D(state.transformation); if (startP != endP) { polyBounds.append(poly.length); poly.append(startP); } if (p.type == SegmentType.Line) { endP = p.p2.affineTransform2D(state.transformation); poly.append(endP); } else { assert(p.type == SegmentType.BezierCubic); wayP1 = p.p2.affineTransform2D(state.transformation); wayP2 = p.p3.affineTransform2D(state.transformation); endP = p.p4.affineTransform2D(state.transformation); float t = 0.0f; while(t < 1.0f) { t += tesselationStep; Vector2f tessP = bezierVector2(startP, wayP1, wayP2, endP, t); poly.append(tessP); } } } auto alphas = New!(float[])(polyBounds.length); polyBounds.append(poly.length); foreach(y; 0.._image.height) foreach(x; 0.._image.width) { auto p = Vector2f(x, y); bool inside = false; foreach(i, ref alpha; alphas) { alpha = pointInPolygonAAFast(p, poly.data[polyBounds[i] .. polyBounds[i+1]]); if (alpha == 1) inside = !inside; } float totalAlpha = inside? 1: 0; if (inside) { foreach(alpha; alphas) { totalAlpha *= alpha == 1? 1: 1 - alpha; } } else { foreach(alpha; alphas) { totalAlpha += alpha == 1? 0: (1 - totalAlpha) * alpha; } } Color4f c = state.fillColor; c.a = c.a * totalAlpha; if (c.a > 0.0f) { _image[x, y] = alphaOver(_image[x, y], c); } } poly.free(); polyBounds.free(); alphas.Delete(); } void drawContour() { Vector2f tp1, tp2, tp3, tp4; foreach(i, ref p; contour.data) { if (p.type == SegmentType.Line) { tp1 = p.p1.affineTransform2D(state.transformation); tp2 = p.p2.affineTransform2D(state.transformation); drawLine(tp1, tp2); } else if (p.type == SegmentType.BezierCubic) { tp1 = p.p1.affineTransform2D(state.transformation); tp2 = p.p2.affineTransform2D(state.transformation); tp3 = p.p3.affineTransform2D(state.transformation); tp4 = p.p4.affineTransform2D(state.transformation); drawBezierCurve(tp1, tp2, tp3, tp4); } } } void drawLineTangent(Vector2f p1, Vector2f p2, Vector2f t1, Vector2f t2) { Vector2f n1 = Vector2f(-t1.y, t1.x); Vector2f n2 = Vector2f(-t2.y, t2.x); Vector2f offset1 = n1 * state.lineWidth * 0.5f; Vector2f offset2 = n2 * state.lineWidth * 0.5f; Vector2f[4] poly; poly[0] = p1 - offset1; poly[1] = p1 + offset1; poly[2] = p2 + offset2; poly[3] = p2 - offset2; float subpSize = 1.0f / subpixelResolution; float subpContrib = 1.0f / (subpixelResolution * subpixelResolution); int xmin = cast(int)min2(min2(poly[0].x, poly[1].x), min2(poly[2].x, poly[3].x)) - 1; int ymin = cast(int)min2(min2(poly[0].y, poly[1].y), min2(poly[2].y, poly[3].y)) - 1; int xmax = cast(int)max2(max2(poly[0].x, poly[1].x), max2(poly[2].x, poly[3].x)) + 1; int ymax = cast(int)max2(max2(poly[0].y, poly[1].y), max2(poly[2].y, poly[3].y)) + 1; foreach(y; ymin..ymax) foreach(x; xmin..xmax) { float alpha = 0.0f; foreach(sy; 0..subpixelResolution) foreach(sx; 0..subpixelResolution) { auto p = Vector2f(x + sx * subpSize, y + sy * subpSize); if (pointInPolygon(p, poly)) alpha += subpContrib; } float srcAlpha = tmpBuffer[x, y].r; tmpBuffer[x, y] = Color4f(min2(srcAlpha + alpha, 1.0f), 0, 0, 1); } } void drawLine(Vector2f p1, Vector2f p2) { Vector2f dir = p2 - p1; Vector2f ndir = dir.normalized; drawLineTangent(p1, p2, ndir, ndir); } void drawBezierCurve(Vector2f a, Vector2f b, Vector2f c, Vector2f d) { Vector2f p1 = a; Vector2f t1 = bezierTangentVector2(a, b, c, d, 0.0f).normalized; float t = 0.0f; while(t < 1.0f) { t += tesselationStep; Vector2f p2 = bezierVector2(a, b, c, d, t); Vector2f t2 = bezierTangentVector2(a, b, c, d, t).normalized; drawLineTangent(p1, p2, t1, t2); p1 = p2; t1 = t2; } } void blitTmpBuffer(Color4f color) { foreach(y; 0.._image.height) foreach(x; 0.._image.width) { Color4f c1 = _image[x, y]; Color4f c2 = color; c2.a = tmpBuffer[x, y].r * color.a; _image[x, y] = alphaOver(c1, c2); } } } bool pointInPolygon(Vector2f p, Vector2f[] poly) { size_t i = 0; size_t j = poly.length - 1; bool inside = false; for (i = 0; i < poly.length; i++) { Vector2f a = poly[i]; Vector2f b = poly[j]; if ((a.y > p.y) != (b.y > p.y) && (p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x)) inside = !inside; j = i; } return inside; } float sqrDistanceToLineSegment(Vector2f a, Vector2f b, Vector2f p) { Vector2f n = b - a; Vector2f pa = a - p; float c = dot(n, pa); if (c > 0.0f) return dot(pa, pa); Vector2f bp = p - b; if (dot(n, bp) > 0.0f) return dot(bp, bp); Vector2f e = pa - n * (c / dot(n, n)); return dot(e, e); } float pointInPolygonAAFast(Vector2f p, Vector2f[] poly) { size_t i = 0; size_t j = poly.length - 1; bool inside = false; float minDistance = float.max; for (i = 0; i < poly.length; i++) { Vector2f a = poly[i]; Vector2f b = poly[j]; float lx = (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x; if ((a.y > p.y) != (b.y > p.y)) { if (p.x < lx) inside = !inside; float dist = sqrDistanceToLineSegment(a, b, p); if (dist < minDistance) minDistance = dist; } j = i; } float cd = 1.0f - clamp(sqrt(minDistance), 0.0f, 1.0f); return max2(cast(float)inside, cd); } ================================================ FILE: dlib/image/color.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * RGBA color space * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.color; import dlib.math.vector; import dlib.math.utils; /// RGBA color channel enum Channel { R = 0, G = 1, B = 2, A = 3 } /// RGBA 16-bit integer color representation (a vector of ushorts) alias Color4 = Vector!(ushort, 4); /// ditto alias ColorRGBA = Color4; Color4 invert(Color4 c) { return Color4( cast(ushort)(255 - c.r), cast(ushort)(255 - c.g), cast(ushort)(255 - c.b), c.a); } /** * RGBA floating-point color representation, * encapsulates Vector4f */ struct Color4f { Vector4f vec; alias vec this; this(Color4 c, uint bitDepth = 8) { float maxv = (2 ^^ bitDepth) - 1; vec.r = c.r / maxv; vec.g = c.g / maxv; vec.b = c.b / maxv; vec.a = c.a / maxv; } this(Color4f c) { vec = c.vec; } this(Vector4f v) { vec = v; } this(Vector3f v) { vec = Vector4f(v.x, v.y, v.z, 1.0f); } this(float cr, float cg, float cb, float ca = 1.0f) { vec = Vector4f(cr, cg, cb, ca); } static Color4f zero() { return Color4f(0.0f, 0.0f, 0.0f, 0.0f); } Color4f opAssign(Color4f c) { vec = c.vec; return this; } Color4f opBinary(string op)(float x) if (op == "+") { return Color4f(this.vec + x); } Color4f opBinary(string op)(float x) if (op == "-") { return Color4f(this.vec - x); } Color4f opBinary(string op)(float x) if (op == "*") { return Color4f(this.vec * x); } Color4f opBinary(string op)(float x) if (op == "/") { return Color4f(this.vec / x); } Color4f opBinary(string op)(Vector4f v) if (op == "+") { return Color4f(this.vec + v); } Color4f opBinary(string op)(Vector4f v) if (op == "-") { return Color4f(this.vec - v); } Color4f opBinary(string op)(Vector4f v) if (op == "*") { return Color4f(this.vec * v); } Color4f opBinary(string op)(Vector4f v) if (op == "/") { return Color4f(this.vec / v); } Color4 convert(int bitDepth) { float maxv = (2 ^^ bitDepth) - 1; return Color4( cast(ushort)(r.clamp(0.0f, 1.0f) * maxv), cast(ushort)(g.clamp(0.0f, 1.0f) * maxv), cast(ushort)(b.clamp(0.0f, 1.0f) * maxv), cast(ushort)(a.clamp(0.0f, 1.0f) * maxv) ); } int opCmp(ref const(Color4f) c) const { return cast(int)((luminance() - c.luminance()) * 100); } alias luminance = luminance709; // ITU-R Rec. BT.709 float luminance709() const { return ( vec.arrayof[0] * 0.2126f + vec.arrayof[1] * 0.7152f + vec.arrayof[2] * 0.0722f ); } // ITU-R Rec. BT.601 float luminance601() const { return ( vec.arrayof[0] * 0.3f + vec.arrayof[1] * 0.59f + vec.arrayof[2] * 0.11f ); } @property Color4f inverse() { return Color4f( 1.0f - vec.r, 1.0f - vec.g, 1.0f - vec.b, vec.a); } @property Color4f clamped(float minv, float maxv) { return Color4f( vec.r.clamp(minv, maxv), vec.g.clamp(minv, maxv), vec.b.clamp(minv, maxv), vec.a.clamp(minv, maxv) ); } /// Converts color from gamma space to linear space Color4f toLinear(float gamma = 2.2f) { float lr = r ^^ gamma; float lg = g ^^ gamma; float lb = b ^^ gamma; return Color4f(lr, lg, lb, a); } /// Converts color from linear space to gamma space Color4f toGamma(float gamma = 2.2f) { float invGamma = 1.0f / gamma; float lr = r ^^ invGamma; float lg = g ^^ invGamma; float lb = b ^^ invGamma; return Color4f(lr, lg, lb, a); } } /// ditto alias ColorRGBAf = Color4f; /// unittest { Color4f c1 = Color4f(0.5f, 0.5f, 0.5f, 1.0f); assert(isConsiderZero(c1.luminance - 0.5f)); assert(isConsiderZero(c1.luminance601 - 0.5f)); Color4f c2 = Color4f(1.0f, 0.0f, 0.0f, 1.0f); assert(isAlmostZero(c2.inverse - Color4f(0.0f, 1.0f, 1.0f, 1.0f))); } /// Encode a normal vector to color Color4f packNormal(Vector3f n) { return Color4f((n + 1.0f) * 0.5f); } /// 24-bit integer color unpacking Color4f color3(int hex) { ubyte r = (hex >> 16) & 255; ubyte g = (hex >> 8) & 255; ubyte b = hex & 255; return Color4f( cast(float)r / 255.0f, cast(float)g / 255.0f, cast(float)b / 255.0f); } /// unittest { assert(color3(0xff0000) == Color4f(1.0f, 0.0f, 0.0f, 1.0f)); } /// 32-bit integer color unpacking Color4f color4(int hex) { ubyte r = (hex >> 24) & 255; ubyte g = (hex >> 16) & 255; ubyte b = (hex >> 8) & 255; ubyte a = hex & 255; return Color4f( cast(float)r / 255.0f, cast(float)g / 255.0f, cast(float)b / 255.0f, cast(float)a / 255.0f); } /// unittest { assert(color4(0xff000000) == Color4f(1.0f, 0.0f, 0.0f, 0.0f)); } /// Blend two colors taking transparency into account Color4f alphaOver(Color4f c1, Color4f c2) { Color4f c; float a = c2.a + c1.a * (1.0f - c2.a); if (a == 0.0f) c = Color4f(0, 0, 0, 0); else { c = (c2 * c2.a + c1 * c1.a * (1.0f - c2.a)) / a; c.a = a; } return c; } /** * Is all elements almost zero */ bool isAlmostZero(Color4f c) { return (isConsiderZero(c.r) && isConsiderZero(c.g) && isConsiderZero(c.b) && isConsiderZero(c.a)); } ================================================ FILE: dlib/image/filters/binarization.d ================================================ /* Copyright (c) 2018-2025 Oleg Baharev, Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Image binarization * * Copyright: Oleg Baharev, Timur Gafarov 2018-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Oleg Baharev, Timur Gafarov */ module dlib.image.filters.binarization; import dlib.image.image; import dlib.image.color; import dlib.image.filters.histogram; int otsuThreshold(SuperImage img) { auto histogram = createHistogram(img); int sumOfLuminances; foreach (x; 0..img.width) foreach (y; 0..img.height) { sumOfLuminances += cast(int)(img[x, y].luminance * 255); } auto allPixelCount = cast(double)(img.width * img.height); int bestThreshold = 0; int firstClassPixelCount = 0; int firstClassLuminanceSum = 0; double bestSigma = 0.0; for (int threshold = 0; threshold < 255; threshold++) { firstClassPixelCount += histogram[threshold]; firstClassLuminanceSum += threshold * histogram[threshold]; double firstClassProbability = firstClassPixelCount / allPixelCount; double secondClassProbability = 1.0 - firstClassProbability; double firstClassMean = (firstClassPixelCount == 0) ? 0 : firstClassLuminanceSum / firstClassPixelCount; double secondClassMean = (sumOfLuminances - firstClassLuminanceSum) / (allPixelCount - firstClassPixelCount); double meanDelta = firstClassMean - secondClassMean; double sigma = firstClassProbability * secondClassProbability * meanDelta * meanDelta; if (sigma > bestSigma) { bestSigma = sigma; bestThreshold = threshold; } } return bestThreshold; } /// Otsu binarization auto otsuBinarization(SuperImage img) { SuperImage res = img.createSameFormat(img.width, img.height); auto threshold = otsuThreshold(img); foreach (x; 0..img.width) foreach (y; 0..img.height) { auto luminance = cast(int)(img[x,y].luminance * 255); if (luminance > threshold) res[x, y] = Color4f(1.0f, 1.0f, 1.0f, 1.0f); else res[x, y] = Color4f(0.0f, 0.0f, 0.0f, 1.0f); } return res; } ================================================ FILE: dlib/image/filters/boxblur.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Box blur * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.filters.boxblur; import dlib.image.color; import dlib.image.image; /// Blur an image SuperImage boxBlur(SuperImage img, SuperImage outp, int radius) { SuperImage res; if (outp) res = outp; else res = img.dup; immutable int boxSide = radius * 2 + 1; immutable int boxSide2 = boxSide * boxSide; foreach(y; 0..img.height) foreach(x; 0..img.width) { float alpha = Color4f(img[x, y]).a; Color4f total = Color4f(0, 0, 0); foreach(ky; 0..boxSide) foreach(kx; 0..boxSide) { int iy = y + (ky - radius); int ix = x + (kx - radius); total += img[ix, iy]; } total /= boxSide2; total.a = alpha; res[x,y] = total; } return res; } /// ditto SuperImage boxBlur(SuperImage img, int radius) { return boxBlur(img, null, radius); } ================================================ FILE: dlib/image/filters/chromakey.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Filters that remove background from images * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.filters.chromakey; import dlib.math.utils; import dlib.image.image; import dlib.image.color; import dlib.image.hsv; import dlib.math.vector; import dlib.math.utils; /// Euclidean distance chroma key SuperImage chromaKeyEuclidean( SuperImage img, SuperImage outp, Color4f keyColor, float minDist, float maxDist) { SuperImage res; if (outp) res = outp; else res = img.dup; foreach(y; img.col) foreach(x; img.row) { Color4f col = img[x, y]; Color4f delta = col - keyColor; float distSqr = dot(delta, delta); col.a = clamp( (distSqr - minDist) / (maxDist - minDist), 0.0f, 1.0f); res[x, y] = col; } return res; } /// ditto SuperImage chromaKeyEuclidean( SuperImage img, Color4f keyColor, float minDist, float maxDist) { return chromaKeyEuclidean(img, null, keyColor, minDist, maxDist); } /// HSV selective scale chroma key SuperImage chromaKey( SuperImage img, SuperImage outp, float hue, float hueToleranceMin = -20.0f, float hueToleranceMax = 20.0f, float satThres = 0.2f, float valThres = 0.3f) { SuperImage res; if (outp) res = outp; else res = img.dup; foreach(x; 0..img.width) foreach(y; 0..img.height) { Color4f col = res[x, y]; ColorHSVAf hsva = ColorHSVAf(col); hsva.selectiveScale( hue, HSVAChannel.A, 0.0f, false, hueToleranceMin, hueToleranceMax, satThres, valThres); res[x, y] = hsva.rgba; } return res; } /// ditto SuperImage chromaKey( SuperImage img, float hue, float hueToleranceMin = -20.0f, float hueToleranceMax = 20.0f, float satThres = 0.2f, float valThres = 0.3f) { return chromaKey(img, null, hue, hueToleranceMin, hueToleranceMax, satThres, valThres); } /// Turns image into b&w where only one color left, using HSV selective scale SuperImage colorPass( SuperImage img, SuperImage outp, float hue, float hueToleranceMin = -20.0f, float hueToleranceMax = 20.0f, float satThres = 0.2f, float valThres = 0.3f) in { assert (img.data.length); } do { SuperImage res; if (outp) res = outp; else res = img.dup; foreach(y; 0..img.height) foreach(x; 0..img.width) { Color4f col = res[x, y]; ColorHSVAf hsva = ColorHSVAf(col); hsva.selectiveScale( hue, HSVAChannel.S, 0.0f, true, hueToleranceMin, hueToleranceMax, satThres, valThres); res[x, y] = hsva.rgba; } return res; } /// ditto SuperImage colorPass( SuperImage img, float hue, float hueToleranceMin = -20.0f, float hueToleranceMax = 20.0f, float satThres = 0.2f, float valThres = 0.3f) { return colorPass(img, null, hue, hueToleranceMin, hueToleranceMax, satThres, valThres); } private: void selectiveScale(ref ColorHSVAf col, float hue, HSVAChannel chan, float scale, bool inverse, float hueToleranceMin = -20.0f, float hueToleranceMax = 20.0f, float satThres = 0.2f, float valThres = 0.3f) { while (hue >= 360.0f) hue -= 360.0f; while (hue < 0.0f) hue += 360.0f; if (col.hueInRange(hue, hueToleranceMin, hueToleranceMax) && col.s > satThres && col.v > valThres) { if (!inverse) col.arrayof[chan] *= scale; } else { if (inverse) col.arrayof[chan] *= scale; } } ================================================ FILE: dlib/image/filters/contrast.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Adjust contrast * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.filters.contrast; import dlib.image.image; import dlib.image.color; /// Contrast method enum ContrastMethod { AverageGray, AverageImage, } /// Adjust contrast SuperImage contrast(SuperImage img, SuperImage outp, float k, ContrastMethod method = ContrastMethod.AverageGray) { SuperImage res; if (outp) res = outp; else res = img.dup; Color4f aver = Color4f(0.0f, 0.0f, 0.0f); if (method == ContrastMethod.AverageGray) { aver = Color4f(0.5f, 0.5f, 0.5f); } else if (method == ContrastMethod.AverageImage) { foreach(y; 0..res.height) foreach(x; 0..res.width) { aver += img[x, y]; } aver /= (res.height * res.width); } foreach(y; 0..res.height) foreach(x; 0..res.width) { auto col = img[x, y]; col = ((col - aver) * k + aver); res[x, y] = col; } return res; } /// ditto SuperImage contrast(SuperImage a, float k, ContrastMethod method = ContrastMethod.AverageGray) { return contrast(a, null, k, method); } ================================================ FILE: dlib/image/filters/convolution.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Image convolution * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.filters.convolution; import std.algorithm; import dlib.image.image; import dlib.image.color; /// Convolve an image with a kernel SuperImage convolve(SuperImage img, SuperImage outp, float[] kernel, uint kw = 3, uint kh = 3, float divisor = 1.0f, float offset = 0.5f, bool normalize = true, bool useAlpha = true) in { assert(img.data.length); assert(kernel.length == kw * kh); } do { SuperImage res; if (outp) res = outp; else res = img.dup; float kernelSum = reduce!((a,b) => a + b)(kernel); foreach(y; 0..img.height) foreach(x; 0..img.width) { float alpha = Color4f(img[x, y]).a; Color4f csum = Color4f(0, 0, 0); foreach(ky; 0..kh) foreach(kx; 0..kw) { int iy = y + (ky - kh/2); int ix = x + (kx - kw/2); // Extend if (ix < 0) ix = 0; if (ix >= img.width) ix = img.width - 1; if (iy < 0) iy = 0; if (iy >= img.height) iy = img.height - 1; // TODO: // Wrap auto pix = Color4f(img[ix, iy]); auto k = kernel[kx + ky * kw]; csum += pix * k; } if (normalize) { offset = 0.0f; divisor = kernelSum; if (divisor == 0.0f) { divisor = 1.0f; offset = 0.5f; } if (divisor < 0.0f) offset = 1.0f; } csum = csum / divisor + offset; if (!useAlpha) csum.a = alpha; res[x,y] = csum; } return res; } /// ditto SuperImage convolve(SuperImage img, float[] kernel, uint kw = 3, uint kh = 3, float divisor = 1.0f, float offset = 0.5f, bool normalize = true, bool useAlpha = true) { return convolve(img, null, kernel, kw, kh, divisor, offset, normalize, useAlpha); } /// Various built-in convolution kernels struct Kernel { enum float[] Identity = [ 0, 0, 0, 0, 1, 0, 0, 0, 0 ], BoxBlur = [ 1, 1, 1, 1, 1, 1, 1, 1, 1 ], GaussianBlur = [ 1, 2, 1, 2, 4, 2, 1, 2, 1 ], Sharpen = [ -1, -1, -1, -1, 11, -1, -1, -1, -1 ], Emboss = [ -1, -1, 0, -1, 0, 1, 0, 1, 1, ], EdgeEmboss = [ -1.0f, -0.5f, -0.0f, -0.5f, 1.0f, 0.5f, -0.0f, 0.5f, 1.0f ], EdgeDetect = [ -1, -1, -1, -1, 8, -1, -1, -1, -1, ], Laplace = [ 0, 1, 0, 1, -4, 1, 0, 1, 0, ]; } ================================================ FILE: dlib/image/filters/desaturate.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Turn image to black and white * * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.filters.desaturate; import dlib.image.image; import dlib.image.color; /// Default desaturate filter alias desaturate = desaturate709; /// ITU-R recommendation BT.709 SuperImage desaturate709(SuperImage img, SuperImage outp = null) { SuperImage res; if (outp) res = outp; else res = img.dup; foreach(y; 0..img.height) foreach(x; 0..img.width) { auto color = img[x, y]; float l = color.luminance709; res[x, y] = Color4f(l, l, l, color.a); } return res; } /// ITU-R recommendation BT.601 SuperImage desaturate601(SuperImage img, SuperImage outp = null) { SuperImage res; if (outp) res = outp; else res = img.dup; foreach(y; 0..img.height) foreach(x; 0..img.width) { auto color = img[x, y]; float l = color.luminance601; res[x, y] = Color4f(l, l, l, color.a); } return res; } ================================================ FILE: dlib/image/filters/edgedetect.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov, Oleg Baharev Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Detect edges on an image * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.filters.edgedetect; import std.math; import dlib.math.vector; import dlib.image.image; import dlib.image.color; import dlib.image.arithmetics; import dlib.image.filters.contrast; import dlib.image.filters.boxblur; import dlib.image.filters.morphology; import dlib.image.filters.convolution; /// Difference of Gaussians SuperImage edgeDetectDoG(SuperImage src, SuperImage outp, int radius1, int radius2, float amount, bool inv = true) { if (outp is null) outp = src.dup; auto blurred1 = boxBlur(src, outp, radius1); SuperImage outp2 = outp.dup; auto blurred2 = boxBlur(src, outp2, radius2); auto mask = subtract(blurred1, blurred2, outp, 1.0f); outp2.free(); auto highcon = contrast(mask, mask, amount, ContrastMethod.AverageImage); if (inv) return invert(highcon, highcon); else return highcon; } /// ditto SuperImage edgeDetectDoG(SuperImage src, int radius1, int radius2, float amount, bool inv = true) { return edgeDetectDoG(src, null, radius1, radius2, amount, inv); } /// Morphologic edge detection SuperImage edgeDetectGradient(SuperImage src, SuperImage outp) { if (outp is null) outp = src.dup; return gradient(src, outp); } /// ditto SuperImage edgeDetectGradient(SuperImage src) { return edgeDetectGradient(src, null); } /// Laplace edge detection SuperImage edgeDetectLaplace(SuperImage src, SuperImage outp) { if (outp is null) outp = src.dup; return convolve(src, outp, Kernel.Laplace, 3, 3, 1.0f, 0.0f, false); } /// ditto SuperImage edgeDetectLaplace(SuperImage src) { return edgeDetectLaplace(src, null); } /// Sobel edge detection SuperImage edgeDetectSobel(SuperImage src, SuperImage outp, float normFactor = 1.0f / 8.0f) { if (outp is null) outp = src.dup; enum float[3][3] sobelHorizontal = [ [-1, 0, 1], [-2, 0, 2], [-1, 0, 1], ]; enum float[3][3] sobelVertical = [ [-1, -2, -1], [ 0, 0, 0], [ 1, 2, 1], ]; foreach(window, x, y; src.windows(3, 3)) { Color4f hor = Color4f(0, 0, 0); Color4f ver = Color4f(0, 0, 0); foreach(ref Color4f pixel, x, y; window) { hor += pixel * sobelHorizontal[y][x]; ver += pixel * sobelVertical[y][x]; } float magnitude = sqrt(hor.xyz.lengthsqr + ver.xyz.lengthsqr) * normFactor; Color4f res = Color4f(magnitude, magnitude, magnitude, 1.0f); res.a = 1.0f; outp[x, y] = res; } return outp; } /// ditto SuperImage edgeDetectSobel(SuperImage src, float normFactor = 1.0f / 8.0f) { return edgeDetectSobel(src, null, normFactor); } ================================================ FILE: dlib/image/filters/histogram.d ================================================ /* Copyright (c) 2018-2025 Oleg Baharev, Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Generate histogram of an image * * Copyright: Oleg Baharev, Timur Gafarov 2018-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Oleg Baharev, Timur Gafarov */ module dlib.image.filters.histogram; import dlib.image.image; import dlib.image.color; /// Obtain histogram int[256] createHistogram(SuperImage img) { int[256] histogram; foreach (x; 0..img.width) foreach (y; 0..img.height) { int luma = cast(int)(img[x,y].luminance * 255); histogram[luma] += 1; } return histogram; } /// Generate histogram image SuperImage histogramImage(SuperImage img, Color4f background, Color4f diagram) { SuperImage res = img.createSameFormat(256, 256); int[256] h = createHistogram(img); int vmax = 0; foreach(v; h) { if (v > vmax) vmax = v; } foreach(ref v; h) { v = cast(int)(cast(float)v / cast(float)vmax * 255.0f); } foreach (x; 0..res.width) foreach (y; 0..res.height) { int v = h[x]; if (y < 255 - v) res[x, y] = background; else res[x, y] = diagram; } return res; } ================================================ FILE: dlib/image/filters/lens.d ================================================ /* Copyright (c) 2014-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Lens distortion filter * * Copyright: Timur Gafarov 2014-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.filters.lens; import std.math; import dlib.image.image; /// Apply lens distortion filter SuperImage lensDistortion( SuperImage img, SuperImage outp, float strength, float zoom, bool interpolation = true) { SuperImage res; if (outp) res = outp; else res = img.dup; float halfWidth = cast(float)img.width / 2.0f; float halfHeight = cast(float)img.height / 2.0f; float correctionRadius = sqrt(cast(float)(img.width ^^ 2 + img.height ^^ 2)) / strength; foreach(y; 0..img.height) foreach(x; 0..img.width) { float newX = x - halfWidth; float newY = y - halfHeight; float distance = sqrt(newX ^^ 2 + newY ^^ 2); float r = distance / correctionRadius; float theta; if (r == 0) theta = 1; else theta = atan(r) / r; float sourceX = (halfWidth + theta * newX * zoom); float sourceY = (halfHeight + theta * newY * zoom); if (interpolation) res[x, y] = img.bilinearPixel(sourceX, sourceY); else res[x, y] = img[cast(int)sourceX, cast(int)sourceY]; } return res; } /// ditto SuperImage lensDistortion( SuperImage img, float strength, float zoom, bool interpolation = true) { return lensDistortion(img, null, strength, zoom, interpolation); } ================================================ FILE: dlib/image/filters/median.d ================================================ /* Copyright (c) 2022-2025 Oleg Baharev, Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Median filter. * * Copyright: Oleg Baharev, Timur Gafarov 2022-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Oleg Baharev, Timur Gafarov */ module dlib.image.filters.median; import std.algorithm: sort; import dlib.core.memory; import dlib.image.image; import dlib.image.color; /// Median filter auto medianFilter(SuperImage img, SuperImage outp, uint windowWidth, uint windowHeight) { SuperImage res; if (outp) res = outp; else res = img.dup; struct ColorAndLuma { Color4f color; float luminance; } ColorAndLuma[] window = New!(ColorAndLuma[])(windowWidth * windowHeight); uint halfWindowWidth = windowWidth / 2; uint halfWindowHeight = windowHeight / 2; foreach(y; 0..img.height) foreach(x; 0..img.width) { foreach(wy; 0..windowHeight) foreach(wx; 0..windowWidth) { int sampleX = x - halfWindowWidth + wx; int sampleY = y - halfWindowHeight + wy; if (sampleX < 0) sampleX = 0; else if (sampleX >= img.width) sampleX = img.width - 1; if (sampleY < 0) sampleY = 0; else if (sampleY >= img.height) sampleY = img.height - 1; auto sample = img[sampleX, sampleY]; auto windowSample = &window[wy * windowWidth + wx]; windowSample.color = sample; windowSample.luminance = sample.luminance; } sort!("a.luminance > b.luminance")(window); res[x, y] = window[$ / 2].color; } Delete(window); return res; } /// ditto SuperImage medianFilter(SuperImage img, uint windowWidth, uint windowHeight) { return medianFilter(img, null, windowWidth, windowHeight); } ================================================ FILE: dlib/image/filters/morphology.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Morphologic filters * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.filters.morphology; import dlib.image.color; import dlib.image.image; import dlib.image.arithmetics; /// Morphologic operation enum MorphOperation { Dilate, Erode } /// Apply morphologic operation SuperImage morphOp(MorphOperation op)(SuperImage img, SuperImage outp) in { assert(img.data.length); } do { // TODO: // add support for other structuring elements // other than box (disk, diamond, etc) SuperImage res; if (outp) res = outp; else res = img.dup; uint kw = 3, kh = 3; foreach(y; 0..img.height) foreach(x; 0..img.width) { static if (op == MorphOperation.Dilate) { Color4f resc = Color4f(0, 0, 0, 1); } static if (op == MorphOperation.Erode) { Color4f resc = img[x, y]; } foreach(ky; 0..kh) foreach(kx; 0..kw) { int iy = y + (ky - kh/2); int ix = x + (kx - kw/2); // Extend if (ix < 0) ix = 0; if (ix >= img.width) ix = img.width - 1; if (iy < 0) iy = 0; if (iy >= img.height) iy = img.height - 1; // TODO: // Wrap auto pix = img[ix, iy]; static if (op == MorphOperation.Dilate) { if (pix > resc) resc = pix; } static if (op == MorphOperation.Erode) { if (pix < resc) resc = pix; } } res[x, y] = resc; } return res; } /// Ditto SuperImage morph(MorphOperation op) (SuperImage img) { return morphOp!(op)(img, null); } /// Dilate alias dilate = morph!(MorphOperation.Dilate); /// Erode alias erode = morph!(MorphOperation.Erode); /// Morphologic open SuperImage open(SuperImage img) { return dilate(erode(img)); } /// ditto SuperImage open(SuperImage img, SuperImage outp) { if (outp is null) outp = img.dup; auto outp2 = outp.dup; auto e = morphOp!(MorphOperation.Erode)(img, outp2); auto d = morphOp!(MorphOperation.Dilate)(outp2, outp); outp2.free(); return d; } /// Morphologic close SuperImage close(SuperImage img) { return erode(dilate(img)); } /// ditto SuperImage close(SuperImage img, SuperImage outp) { if (outp is null) outp = img.dup; auto outp2 = outp.dup; auto d = morphOp!(MorphOperation.Dilate)(img, outp2); auto e = morphOp!(MorphOperation.Erode)(outp2, outp); outp2.free(); return e; } /// Morphologic gradient SuperImage gradient(SuperImage img) { return subtract(dilate(img), erode(img)); } /// ditto SuperImage gradient(SuperImage img, SuperImage outp) { if (outp is null) outp = img.dup; auto outp2 = outp.dup; auto d = morphOp!(MorphOperation.Dilate)(img, outp2); auto e = morphOp!(MorphOperation.Erode)(img, outp); auto s = subtract(d, e, outp); outp2.free(); return s; } /// White top-hat transform SuperImage topHatWhite(SuperImage img) { return subtract(img, open(img)); } /// ditto SuperImage topHatWhite(SuperImage img, SuperImage outp) { if (outp is null) outp = img.dup; auto o = open(img, outp); auto s = subtract(img, o, outp); return s; } /// Black top-hat transform SuperImage topHatBlack(SuperImage img) { return subtract(img, close(img)); } /// ditto SuperImage topHatBlack(SuperImage img, SuperImage outp) { if (outp is null) outp = img.dup; auto o = close(img, outp); auto s = subtract(img, o, outp); return s; } ================================================ FILE: dlib/image/filters/normalmap.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Normal map generation * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.filters.normalmap; import dlib.image.image; import dlib.image.color; import dlib.math.vector; /// Generate normal map from height map using Sobel operator SuperImage heightToNormal( SuperImage img, SuperImage outp, Channel channel = Channel.R, float strength = 2.0f) in { assert (img.data.length); } do { // TODO: optionally transfer height data to alpha channel SuperImage res; if (outp) res = outp; else res = img.dup; if (img.channels == 1) channel = Channel.R; float[8] sobelTaps; foreach(y; 0..img.height) foreach(x; 0..img.width) { sobelTaps[0] = img[x-1, y-1][channel]; sobelTaps[1] = img[x, y-1][channel]; sobelTaps[2] = img[x+1, y-1][channel]; sobelTaps[3] = img[x-1, y+1][channel]; sobelTaps[4] = img[x, y+1][channel]; sobelTaps[5] = img[x+1, y+1][channel]; sobelTaps[6] = img[x-1, y ][channel]; sobelTaps[7] = img[x+1, y ][channel]; float dx, dy; // Do y sobel filter dy = sobelTaps[0] * +1.0f; dy += sobelTaps[1] * +2.0f; dy += sobelTaps[2] * +1.0f; dy += sobelTaps[3] * -1.0f; dy += sobelTaps[4] * -2.0f; dy += sobelTaps[5] * -1.0f; // Do x sobel filter dx = sobelTaps[0] * -1.0f; dx += sobelTaps[6] * -2.0f; dx += sobelTaps[3] * -1.0f; dx += sobelTaps[2] * +1.0f; dx += sobelTaps[7] * +2.0f; dx += sobelTaps[5] * +1.0f; // pack normal into floating-point RGBA Vector3f normal = Vector3f(-dx, -dy, 1.0f / strength); Color4f col = packNormal(normal); col.a = 1.0f; // write result res[x, y] = col; } return res; } /// ditto SuperImage heightToNormal( SuperImage img, Channel channel = Channel.R, float strength = 2.0f) { return heightToNormal(img, null, channel, strength); } ================================================ FILE: dlib/image/filters/package.d ================================================ /* Copyright (c) 2020-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Image filtering * * Copyright: Timur Gafarov 2020-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.filters; public { import dlib.image.filters.boxblur; import dlib.image.filters.chromakey; import dlib.image.filters.convolution; import dlib.image.filters.desaturate; import dlib.image.filters.edgedetect; import dlib.image.filters.lens; import dlib.image.filters.median; import dlib.image.filters.morphology; import dlib.image.filters.normalmap; import dlib.image.filters.sharpen; import dlib.image.filters.contrast; import dlib.image.filters.histogram; import dlib.image.filters.binarization; } ================================================ FILE: dlib/image/filters/sharpen.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Image sharpening * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.filters.sharpen; import dlib.image.image; import dlib.image.color; import dlib.image.arithmetics; import dlib.image.filters.contrast; import dlib.image.filters.boxblur; /// Sharpen an image SuperImage sharpen(SuperImage src, SuperImage outp, int radius, float amount) { if (outp is null) outp = src.dup; auto blurred = boxBlur(src, outp, radius); auto mask = subtract(src, blurred, outp, 1.0f); auto highcon = contrast(mask, outp, amount, ContrastMethod.AverageImage); return add(src, highcon, outp, 0.25f); } /// ditto SuperImage sharpen(SuperImage src, int radius, float amount) { return sharpen(src, null, radius, amount); } ================================================ FILE: dlib/image/fthread.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Threaded image filtering * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.fthread; import dlib.core.memory; import dlib.core.thread; import dlib.image.image; /** * An object that applies a filter function to an image in a separate thread */ class FilteringThread { Thread thread; protected SuperImage image; protected SuperImage output; this(SuperImage img) { thread = New!Thread(&threadFunc); image = img; output = img; } ~this() { Delete(thread); } void threadFunc() { run(); } SuperImage filtered() { thread.start(); while(thread.isRunning) onRunning(); onFinished(); return output; } /// Called in a second thread. Override it void run() {} /// Called in main thread in a loop while second thread is running. Override it void onRunning() {} /// Called in main thread once when second thread finishes. Override it void onFinished() {} } ================================================ FILE: dlib/image/hdri.d ================================================ /* Copyright (c) 2014-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * High dynamic range images * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.hdri; import core.stdc.string; import std.math; import dlib.core.memory; import dlib.image.image; import dlib.image.color; import dlib.math.vector; import dlib.math.utils; /// Linear floating-point pixel formats enum FloatPixelFormat: uint { RGBAF32 = 8 //TODO: //RGBAF64 = 9 //RGBAF16 = 10 } /** * HDR image interface */ abstract class SuperHDRImage: SuperImage { override @property uint pixelFormat() { return FloatPixelFormat.RGBAF32; } } /** * Extension of standard Image that is based on FloatPixelFormat.RGBAF32 */ class HDRImage: SuperHDRImage { public: @property uint width() { return _width; } @property uint height() { return _height; } @property uint bitDepth() { return _bitDepth; } @property uint channels() { return _channels; } @property uint pixelSize() { return _pixelSize; } @property ubyte[] data() { return _data; } @property SuperImage dup() { auto res = new HDRImage(_width, _height); res.data[] = data[]; return res; } SuperImage createSameFormat(uint w, uint h) { return new HDRImage(w, h); } this(uint w, uint h) { _width = w; _height = h; _bitDepth = 32; _channels = 4; _pixelSize = (_bitDepth / 8) * _channels; allocateData(); } Color4f opIndex(int x, int y) { while(x >= _width) x = _width-1; while(y >= _height) y = _height-1; while(x < 0) x = 0; while(y < 0) y = 0; float r, g, b, a; auto dataptr = data.ptr + (y * _width + x) * _pixelSize; memcpy(&r, dataptr, 4); memcpy(&g, dataptr + 4, 4); memcpy(&b, dataptr + 4 * 2, 4); memcpy(&a, dataptr + 4 * 3, 4); return Color4f(r, g, b, a); } Color4f opIndexAssign(Color4f c, int x, int y) { while(x >= _width) x = _width-1; while(y >= _height) y = _height-1; while(x < 0) x = 0; while(y < 0) y = 0; auto dataptr = data.ptr + (y * _width + x) * _pixelSize; memcpy(dataptr, &c.arrayof[0], 4); memcpy(dataptr + 4, &c.arrayof[1], 4); memcpy(dataptr + 4 * 2, &c.arrayof[2], 4); memcpy(dataptr + 4 * 3, &c.arrayof[3], 4); return c; } protected void allocateData() { _data = new ubyte[_width * _height * _pixelSize]; } void free() { // Do nothing, let GC delete the object } protected: uint _width; uint _height; uint _bitDepth; uint _channels; uint _pixelSize; ubyte[] _data; } /// Clamp pixels luminance to a specified range SuperImage clamp(SuperImage img, float minv, float maxv) { foreach(x; 0..img.width) foreach(y; 0..img.height) { img[x, y] = img[x, y].clamped(minv, maxv); } return img; } /** * Factory interface for HDR images */ interface SuperHDRImageFactory { SuperHDRImage createImage(uint w, uint h); } /** * Factory class for HDR images */ class HDRImageFactory: SuperHDRImageFactory { SuperHDRImage createImage(uint w, uint h) { return new HDRImage(w, h); } } private SuperHDRImageFactory _defaultHDRImageFactory; /** * Get default SuperHDRImageFactory singleton */ SuperHDRImageFactory defaultHDRImageFactory() { if (!_defaultHDRImageFactory) _defaultHDRImageFactory = new HDRImageFactory(); return _defaultHDRImageFactory; } /** * HDRImage that uses dlib.core.memory instead of GC */ class UnmanagedHDRImage: HDRImage { override @property SuperImage dup() { auto res = New!(UnmanagedHDRImage)(_width, _height); res.data[] = data[]; return res; } override SuperImage createSameFormat(uint w, uint h) { return New!(UnmanagedHDRImage)(w, h); } this(uint w, uint h) { super(w, h); } ~this() { Delete(_data); } protected override void allocateData() { _data = New!(ubyte[])(_width * _height * _pixelSize); } override void free() { Delete(this); } } /** * Factory class for UnmanagedHDRImageFactory */ class UnmanagedHDRImageFactory: SuperHDRImageFactory { SuperHDRImage createImage(uint w, uint h) { return New!UnmanagedHDRImage(w, h); } } /// Simple exponentiation tonal compression SuperImage hdrTonemapGamma(SuperHDRImage img, SuperImage output, float gamma) { SuperImage res; if (output) res = output; else res = image(img.width, img.height, img.channels); foreach(y; 0..img.height) foreach(x; 0..img.width) { Color4f c = img[x, y]; float r = c.r ^^ gamma; float g = c.g ^^ gamma; float b = c.b ^^ gamma; res[x, y] = Color4f(r, g, b, c.a); } return res; } /// ditto SuperImage hdrTonemapGamma(SuperHDRImage img, float gamma) { return hdrTonemapGamma(img, null, gamma); } /// Reinhard tonal compression SuperImage hdrTonemapReinhard(SuperHDRImage img, SuperImage output, float exposure, float gamma) { SuperImage res; if (output) res = output; else res = image(img.width, img.height, img.channels); foreach(y; 0..img.height) foreach(x; 0..img.width) { Color4f c = img[x, y]; Vector3f v = c * exposure; v = v / (v + 1.0f); float r = v.r ^^ gamma; float g = v.g ^^ gamma; float b = v.b ^^ gamma; res[x, y] = Color4f(r, g, b, c.a); } return res; } /// ditto SuperImage hdrTonemapReinhard(SuperHDRImage img, float exposure, float gamma) { return hdrTonemapReinhard(img, null, exposure, gamma); } /// Hable (Uncharted 2) tonal compression SuperImage hdrTonemapHable(SuperHDRImage img, SuperImage output, float exposure, float gamma) { SuperImage res; if (output) res = output; else res = image(img.width, img.height, img.channels); foreach(y; 0..img.height) foreach(x; 0..img.width) { Color4f c = img[x, y]; Vector3f v = c * exposure; Vector3f one = Vector3f(1.0f, 1.0f, 1.0f); Vector3f W = Vector3f(11.2f, 11.2f, 11.2f); v = hableFunc(v * 2.0f) * (one / hableFunc(W)); float r = v.r ^^ gamma; float g = v.g ^^ gamma; float b = v.b ^^ gamma; res[x, y] = Color4f(r, g, b, c.a); } return res; } /// ditto SuperImage hdrTonemapHable(SuperHDRImage img, float exposure, float gamma) { return hdrTonemapHable(img, null, exposure, gamma); } Vector3f hableFunc(Vector3f x) { 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; } /// ACES curve tonal compression SuperImage hdrTonemapACES(SuperHDRImage img, SuperImage output, float exposure, float gamma) { SuperImage res; if (output) res = output; else res = image(img.width, img.height, img.channels); float a = 2.51; float b = 0.03; float c = 2.43; float d = 0.59; float e = 0.14; foreach(y; 0..img.height) foreach(x; 0..img.width) { Color4f col = img[x, y]; Color4f v = col * exposure * 0.6; v = ((v * (v * a + b)) / (v * (v * c + d) + e)).clamped(0.0, 1.0); res[x, y] = Color4f( v.r ^^ gamma, v.g ^^ gamma, v.b ^^ gamma, col.a); } return res; } /// ditto SuperImage hdrTonemapACES(SuperHDRImage img, float exposure, float gamma) { return hdrTonemapACES(img, null, exposure, gamma); } /// Average luminance tonal compression SuperImage hdrTonemapAverageLuminance(SuperHDRImage img, SuperImage output, float a, float gamma) { SuperImage res; if (output) res = output; else res = image(img.width, img.height, img.channels); float lumAverage = averageLuminance(img); float aOverLumAverage = a / lumAverage; foreach(y; 0..img.height) foreach(x; 0..img.width) { auto col = img[x, y]; float Lw = col.luminance; float L = Lw * aOverLumAverage; float Ld = L / (1.0f + L); Color4f nRGB = col / Lw; Color4f dRGB = nRGB * Ld; float r = dRGB.r ^^ gamma; float g = dRGB.g ^^ gamma; float b = dRGB.b ^^ gamma; res[x, y] = Color4f(r, g, b, col.a); } return res; } /// ditto SuperImage hdrTonemapAverageLuminance(SuperHDRImage img, float a, float gamma) { return hdrTonemapAverageLuminance(img, null, a, gamma); } float averageLuminance(SuperHDRImage img) { float sumLuminance = 0.0f; foreach(y; 0..img.height) foreach(x; 0..img.width) { sumLuminance += log(EPSILON + img[x, y].luminance); } float N = img.width * img.height; float lumAverage = exp(sumLuminance / N); return lumAverage; } ================================================ FILE: dlib/image/hsv.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * HSV color space * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.hsv; import std.algorithm; import dlib.math.utils; import dlib.image.color; /// HSV color channel enum HSVAChannel { H = 0, S = 1, V = 2, A = 3 } /** * HSV floating-point color representation */ struct ColorHSVAf { union { struct { float h; float s; float v; float a; } float[4] arrayof; } this(float h, float s, float v, float a) { this.h = h; this.s = s; this.v = v; this.a = a; } this(Color4f c) { a = c.a; float cmin, cmax, delta; cmin = min(c.r, c.g, c.b); cmax = max(c.r, c.g, c.b); v = cmax; delta = cmax - cmin; if (cmax > 0.0f) s = delta / cmax; else { // r = g = b = 0 // s = 0, h is undefined s = 0.0f; h = float.nan; return; } if (c.r >= cmax) h = (c.g - c.b) / delta; else { if (c.g >= cmax) h = 2.0f + (c.b - c.r) / delta; else h = 4.0f + (c.r - c.g) / delta; } h *= 60.0f; if (h < 0.0f) h += 360.0f; } Color4f rgba() { Color4f res; res.a = a; if (s <= 0.0f) { res.r = res.g = res.b = v; return res; } float hh = h; if (hh >= 360.0f) hh = 0.0f; hh /= 60.0f; int i = cast(int)hh; float ff = hh - i; float p = v * (1.0f - s); float q = v * (1.0f - (s * ff)); float t = v * (1.0f - (s * (1.0f - ff))); switch(i) { case 0: res.r = v; res.g = t; res.b = p; break; case 1: res.r = q; res.g = v; res.b = p; break; case 2: res.r = p; res.g = v; res.b = t; break; case 3: res.r = p; res.g = q; res.b = v; break; case 4: res.r = t; res.g = p; res.b = v; break; case 5: default: res.r = v; res.g = p; res.b = q; break; } return res; } void shiftHue(float degrees) { h += degrees; while (h >= 360.0f) h -= 360.0f; while (h < 0.0f) h += 360.0f; } void shiftSaturation(float val) { s += val; s = clamp(s, 0.0f, 1.0f); } void scaleSaturation(float val) { s *= val; s = clamp(s, 0.0f, 1.0f); } void shiftValue(float val) { v += val; v = clamp(v, 0.0f, 1.0f); } void scaleValue(float val) { v *= val; v = clamp(v, 0.0f, 1.0f); } bool hueInRange(float hue2, float tmin, float tmax) { if (h == hue2) return true; float h1 = hue2 + tmin; while (h1 >= 360.0f) h1 -= 360.0f; while (h1 < 0.0f) h1 += 360.0f; float h2 = hue2 + tmax; while (h2 >= 360.0f) h2 -= 360.0f; while (h1 < 0.0f) h2 += 360.0f; return (h1 > h2)? (h > h1 || h < h2): (h > h1 && h < h2); } } /// RGBA from HSV[A] Color4f hsv(float h, float s, float v, float a = 1.0f) { return ColorHSVAf(h, s, v, a).rgba; } ================================================ FILE: dlib/image/image.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Generic image interface and its implementations for integer pixel formats * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.image; import std.stdio; import std.math; import std.conv; import std.range; import dlib.core.memory; import dlib.math.vector; import dlib.math.interpolation; import dlib.image.color; /// sRGBa integer pixel formats, 8 and 16 bits per channel enum IntegerPixelFormat: uint { L8 = 0, LA8 = 1, RGB8 = 2, RGBA8 = 3, L16 = 4, LA16 = 5, RGB16 = 6, RGBA16 = 7 } /** * Abstract image interface */ interface SuperImage: Freeable { /** * Image width in pixels */ @property uint width(); /** * Image height in pixels */ @property uint height(); /** * Bits per channel */ @property uint bitDepth(); /** * Number of channels */ @property uint channels(); /** * Bytes per pixel */ @property uint pixelSize(); /** * This is compatible with IntegerPixelFormat and other internal format enums in dlib. * Values from 0 to 255 are reserved for dlib. * Values 256 and above are application-specific and can be used for custom SuperImage implementations */ @property uint pixelFormat(); /** * Returns raw buffer of image data in scan order. * Pixel layout is specified by pixelFormat */ @property ubyte[] data(); /** * Pixel access operator. * Should always return floating-point sRGBa or linear RGBa, * depending on format family (IntegerPixelFormat or FloatPixelFormat) */ Color4f opIndex(int x, int y); /** * Pixel assignment operator. * Accepts floating-point sRGBa or linear RGBa, * depending on format family (IntegerPixelFormat or FloatPixelFormat) */ Color4f opIndexAssign(Color4f c, int x, int y); /** * Makes a copy of the image */ @property SuperImage dup(); /** * Makes a blank image of the same format */ SuperImage createSameFormat(uint w, uint h); /** * Range of x pixel indices */ final @property auto row() { return iota(0, width); } /** * Range of y pixel indices */ final @property auto col() { return iota(0, height); } /** * Enumerates all pixels of the image in scan order */ final int opApply(scope int delegate(ref Color4f p, uint x, uint y) dg) { int result = 0; foreach(uint y; col) { foreach(uint x; row) { Color4f col = opIndex(x, y); result = dg(col, x, y); opIndexAssign(col, x, y); if (result) break; } if (result) break; } return result; } } /** * SuperImage implementation template for integer pixel formats */ class Image(IntegerPixelFormat fmt): SuperImage { public: override @property uint width() { return _width; } override @property uint height() { return _height; } override @property uint bitDepth() { return _bitDepth; } override @property uint channels() { return _channels; } override @property uint pixelSize() { return _pixelSize; } override @property uint pixelFormat() { return fmt; } override @property ubyte[] data() { return _data; } override @property SuperImage dup() { auto res = new Image!(fmt)(_width, _height); res.data[] = data[]; return res; } override SuperImage createSameFormat(uint w, uint h) { return new Image!(fmt)(w, h); } this(uint w, uint h) { _width = w; _height = h; _bitDepth = [ IntegerPixelFormat.L8: 8, IntegerPixelFormat.LA8: 8, IntegerPixelFormat.RGB8: 8, IntegerPixelFormat.RGBA8: 8, IntegerPixelFormat.L16: 16, IntegerPixelFormat.LA16: 16, IntegerPixelFormat.RGB16: 16, IntegerPixelFormat.RGBA16: 16 ][fmt]; _channels = [ IntegerPixelFormat.L8: 1, IntegerPixelFormat.LA8: 2, IntegerPixelFormat.RGB8: 3, IntegerPixelFormat.RGBA8: 4, IntegerPixelFormat.L16: 1, IntegerPixelFormat.LA16: 2, IntegerPixelFormat.RGB16: 3, IntegerPixelFormat.RGBA16: 4 ][fmt]; _pixelSize = (_bitDepth / 8) * _channels; enum maxDimension = int.max; if (w > maxDimension) { writeln("Image data is not allocated. Exceeded maximum image width ", maxDimension); return; } if (h > maxDimension) { writeln("Image data is not allocated. Exceeded maximum image height ", maxDimension); return; } allocateData(); } protected void allocateData() { size_t size = cast(size_t)_width * cast(size_t)_height * cast(size_t)_pixelSize; _data = new ubyte[size]; } public Color4 getPixel(int x, int y) { ubyte[] pixData = data(); if (x >= width) x = width-1; else if (x < 0) x = 0; if (y >= height) y = height-1; else if (y < 0) y = 0; size_t index = (cast(size_t)y * cast(size_t)_width + cast(size_t)x) * cast(size_t)_pixelSize; auto maxv = (2 ^^ bitDepth) - 1; static if (fmt == IntegerPixelFormat.L8) { auto v = pixData[index]; return Color4(v, v, v); } else if (fmt == IntegerPixelFormat.LA8) { auto v = pixData[index]; return Color4(v, v, v, pixData[index+1]); } else if (fmt == IntegerPixelFormat.RGB8) { return Color4(pixData[index], pixData[index+1], pixData[index+2], cast(ubyte)maxv); } else if (fmt == IntegerPixelFormat.RGBA8) { return Color4(pixData[index], pixData[index+1], pixData[index+2], pixData[index+3]); } else if (fmt == IntegerPixelFormat.L16) { ushort v = pixData[index] << 8 | pixData[index+1]; return Color4(v, v, v); } else if (fmt == IntegerPixelFormat.LA16) { ushort v = pixData[index] << 8 | pixData[index+1]; ushort a = pixData[index+2] << 8 | pixData[index+3]; return Color4(v, v, v, a); } else if (fmt == IntegerPixelFormat.RGB16) { ushort r = pixData[index] << 8 | pixData[index+1]; ushort g = pixData[index+2] << 8 | pixData[index+3]; ushort b = pixData[index+4] << 8 | pixData[index+5]; ushort a = cast(ushort)maxv; return Color4(r, g, b, a); } else if (fmt == IntegerPixelFormat.RGBA16) { ushort r = pixData[index] << 8 | pixData[index+1]; ushort g = pixData[index+2] << 8 | pixData[index+3]; ushort b = pixData[index+4] << 8 | pixData[index+5]; ushort a = pixData[index+6] << 8 | pixData[index+7]; return Color4(r, g, b, a); } else { assert (0, "Image.opIndex is not implemented for IntegerPixelFormat." ~ to!string(fmt)); } } public Color4 setPixel(Color4 c, int x, int y) { ubyte[] pixData = data(); if (x >= width || y >= height || x < 0 || y < 0) return c; size_t index = (cast(size_t)y * cast(size_t)_width + cast(size_t)x) * cast(size_t)_pixelSize; static if (fmt == IntegerPixelFormat.L8) { pixData[index] = cast(ubyte)c.r; } else if (fmt == IntegerPixelFormat.LA8) { pixData[index] = cast(ubyte)c.r; pixData[index+1] = cast(ubyte)c.a; } else if (fmt == IntegerPixelFormat.RGB8) { pixData[index] = cast(ubyte)c.r; pixData[index+1] = cast(ubyte)c.g; pixData[index+2] = cast(ubyte)c.b; } else if (fmt == IntegerPixelFormat.RGBA8) { pixData[index] = cast(ubyte)c.r; pixData[index+1] = cast(ubyte)c.g; pixData[index+2] = cast(ubyte)c.b; pixData[index+3] = cast(ubyte)c.a; } else if (fmt == IntegerPixelFormat.L16) { pixData[index] = cast(ubyte)(c.r >> 8); pixData[index+1] = cast(ubyte)(c.r & 0xFF); } else if (fmt == IntegerPixelFormat.LA16) { pixData[index] = cast(ubyte)(c.r >> 8); pixData[index+1] = cast(ubyte)(c.r & 0xFF); pixData[index+2] = cast(ubyte)(c.a >> 8); pixData[index+3] = cast(ubyte)(c.a & 0xFF); } else if (fmt == IntegerPixelFormat.RGB16) { pixData[index] = cast(ubyte)(c.r >> 8); pixData[index+1] = cast(ubyte)(c.r & 0xFF); pixData[index+2] = cast(ubyte)(c.g >> 8); pixData[index+3] = cast(ubyte)(c.g & 0xFF); pixData[index+4] = cast(ubyte)(c.b >> 8); pixData[index+5] = cast(ubyte)(c.b & 0xFF); } else if (fmt == IntegerPixelFormat.RGBA16) { pixData[index] = cast(ubyte)(c.r >> 8); pixData[index+1] = cast(ubyte)(c.r & 0xFF); pixData[index+2] = cast(ubyte)(c.g >> 8); pixData[index+3] = cast(ubyte)(c.g & 0xFF); pixData[index+4] = cast(ubyte)(c.b >> 8); pixData[index+5] = cast(ubyte)(c.b & 0xFF); pixData[index+6] = cast(ubyte)(c.a >> 8); pixData[index+7] = cast(ubyte)(c.a & 0xFF); } else { assert (0, "Image.opIndexAssign is not implemented for IntegerPixelFormat." ~ to!string(fmt)); } return c; } override Color4f opIndex(int x, int y) { return Color4f(getPixel(x, y), _bitDepth); } override Color4f opIndexAssign(Color4f c, int x, int y) { setPixel(c.convert(_bitDepth), x, y); return c; } void free() { // Do nothing, let GC delete the object } protected: uint _width; uint _height; uint _bitDepth; uint _channels; uint _pixelSize; ubyte[] _data; } /// Specialization of Image for 8-bit luminance pixel format alias ImageL8 = Image!(IntegerPixelFormat.L8); /// Specialization of Image for 8-bit luminance-alpha pixel format alias ImageLA8 = Image!(IntegerPixelFormat.LA8); /// Specialization of Image for 8-bit RGB pixel format alias ImageRGB8 = Image!(IntegerPixelFormat.RGB8); /// Specialization of Image for 8-bit RGBA pixel format alias ImageRGBA8 = Image!(IntegerPixelFormat.RGBA8); /// Specialization of Image for 16-bit luminance pixel format alias ImageL16 = Image!(IntegerPixelFormat.L16); /// Specialization of Image for 16-bit luminance-alpha pixel format alias ImageLA16 = Image!(IntegerPixelFormat.LA16); /// Specialization of Image for 16-bit RGB pixel format alias ImageRGB16 = Image!(IntegerPixelFormat.RGB16); /// Specialization of Image for 16-bit RGBA pixel format alias ImageRGBA16 = Image!(IntegerPixelFormat.RGBA16); /** * All-in-one image factory interface */ interface SuperImageFactory { SuperImage createImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1); } /** * All-in-one image factory class */ class ImageFactory: SuperImageFactory { SuperImage createImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1) { return image(w, h, channels, bitDepth); } } private SuperImageFactory _defaultImageFactory; /** * Get default image factory singleton */ SuperImageFactory defaultImageFactory() { if (!_defaultImageFactory) _defaultImageFactory = new ImageFactory(); return _defaultImageFactory; } /// Create an image with specified parameters SuperImage image(uint w, uint h, uint channels = 3, uint bitDepth = 8) in { assert(channels > 0 && channels <= 4); assert(bitDepth == 8 || bitDepth == 16); } do { switch(channels) { case 1: { if (bitDepth == 8) return new ImageL8(w, h); else return new ImageL16(w, h); } case 2: { if (bitDepth == 8) return new ImageLA8(w, h); else return new ImageLA16(w, h); } case 3: { if (bitDepth == 8) return new ImageRGB8(w, h); else return new ImageRGB16(w, h); } case 4: { if (bitDepth == 8) return new ImageRGBA8(w, h); else return new ImageRGBA16(w, h); } default: assert(0); } } /// Convert image to specified pixel format T convert(T)(SuperImage img) { auto res = new T(img.width, img.height); foreach(x; 0..img.width) foreach(y; 0..img.height) res[x, y] = img[x, y]; return res; } /// Get interpolated pixel value from an image Color4f bilinearPixel(SuperImage img, float x, float y) { real intX; real fracX = modf(x, intX); real intY; real fracY = modf(y, intY); Color4f c1 = img[cast(int)intX, cast(int)intY]; Color4f c2 = img[cast(int)(intX + 1.0f), cast(int)intY]; Color4f c3 = img[cast(int)(intX + 1.0f), cast(int)(intY + 1.0f)]; Color4f c4 = img[cast(int)intX, cast(int)(intY + 1.0f)]; Color4f ic1 = lerp(c1, c2, fracX); Color4f ic2 = lerp(c4, c3, fracX); Color4f ic3 = lerp(ic1, ic2, fracY); return ic3; } /** * Rectangular region of an image that can be iterated with foreach */ struct ImageRegion { SuperImage img; uint xstart; uint ystart; uint width; uint height; final int opApply(scope int delegate(ref Color4f p, uint x, uint y) dg) { int result = 0; uint x1, y1; foreach(uint y; 0..height) { y1 = ystart + y; foreach(uint x; 0..width) { x1 = xstart + x; Color4f col = img[x1, y1]; result = dg(col, x, y); img[x1, y1] = col; if (result) break; } if (result) break; } return result; } } /// ImageRegion factory function ImageRegion region(SuperImage img, uint x, uint y, uint width, uint height) { return ImageRegion(img, x, y, width, height); } /** An InputRange of windows (regions around pixels) of an image that can be iterated with foreach */ struct ImageWindowRange { SuperImage img; uint width; uint height; private uint halfWidth; private uint halfHeight; private uint wx = 0; private uint wy = 0; this(SuperImage img, uint w, uint h) { this.img = img; this.width = w; this.height = h; this.halfWidth = this.width / 2; this.halfHeight = this.height / 2; } final int opApply(scope int delegate(ImageRegion w, uint x, uint y) dg) { int result = 0; foreach(uint y; img.col) { uint ystart = y - halfWidth; foreach(uint x; img.row) { uint xstart = x - halfHeight; auto window = region(img, xstart, ystart, width, height); result = dg(window, x, y); if (result) break; } if (result) break; } return result; } bool empty = false; void popFront() { wx++; if (wx == img.width) { wx = 0; wy++; if (wy == img.height) { wy = 0; empty = true; } } } @property ImageRegion front() { return region(img, wx - halfWidth, wy - halfHeight, width, height); } } /** ImageWindowRange factory function Examples: --- // Convolution with emboss kernel float[3][3] kernel = [ [-1, -1, 0], [-1, 0, 1], [ 0, 1, 1], ]; foreach(window, x, y; inputImage.windows(3, 3)) { Color4f sum = Color4f(0, 0, 0); foreach(ref Color4f pixel, x, y; window) sum += pixel * kernel[y][x]; outputImage[x, y] = sum / 4.0f + 0.5f; } --- */ ImageWindowRange windows(SuperImage img, uint width, uint height) { return ImageWindowRange(img, width, height); } ================================================ FILE: dlib/image/io/bmp.d ================================================ /* Copyright (c) 2014-2025 Timur Gafarov, Roman Chistokhodov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Decode and encode BMP images * * Copyright: Timur Gafarov, Roman Chistokhodov 2014-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov, Roman Chistokhodov */ module dlib.image.io.bmp; import std.stdio; import dlib.core.stream; import dlib.core.memory; import dlib.core.compound; import dlib.image.image; import dlib.image.color; import dlib.image.io; import dlib.image.io.utils; import dlib.filesystem.local; // uncomment this to see debug messages: //version = BMPDebug; static const ubyte[2] BMPMagic = ['B', 'M']; struct BMPFileHeader { ubyte[2] type; // magic number "BM" uint size; // file size ushort reserved1; ushort reserved2; uint offset; // offset to image data } struct BMPInfoHeader { uint size; // size of bitmap info header int width; // image width int height; // image height ushort planes; // must be equal to 1 ushort bitsPerPixel; // bits per pixel uint compression; // compression type uint imageSize; // size of pixel data int xPixelsPerMeter; // pixels per meter on x-axis int yPixelsPerMeter; // pixels per meter on y-axis uint colorsUsed; // number of used colors uint colorsImportant; // number of important colors } struct BMPCoreHeader { uint size; // size of bitmap core header ushort width; // image with ushort height; // image height ushort planes; // must be equal to 1 ushort bitsPerPixel; // bits per pixel } struct BMPCoreInfo { BMPCoreHeader header; ubyte[3] colors; } enum BMPOSType { Win, OS2 } // BMP compression type constants enum BMPCompressionType { RGB = 0, RLE8 = 1, RLE4 = 2, BitFields = 3 } // RLE byte type constants enum RLE { Command = 0, EndOfLine = 0, EndOfBitmap = 1, Delta = 2 } enum BMPInfoSize { OLD = 12, WIN = 40, OS2 = 64, WIN4 = 108, WIN5 = 124, } class BMPLoadException: ImageLoadException { this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { super(msg, file, line, next); } } private ubyte calculateShift(uint mask) nothrow pure { ubyte result = 0; while (mask && !(mask & 1)) { result++; mask >>= 1; } return result; } unittest { assert(calculateShift(0xff) == 0); assert(calculateShift(0xff00) == 8); assert(calculateShift(0xff0000) == 16); assert(calculateShift(0xff000000) == 24); } private ubyte applyMask(uint value, uint mask, ubyte shift, ubyte scale) nothrow pure { return cast(ubyte) (((value & mask) >> shift) * scale); } private ubyte calculateScale(uint mask, ubyte shift) nothrow pure { return cast(ubyte) (256 / calculateDivisor(mask, shift)); } private uint calculateDivisor(uint mask, ubyte shift) nothrow pure { return (mask >> shift) + 1; } private bool checkIndex(uint index, const(ubyte)[] colormap) nothrow pure { return index + 2 < colormap.length; } /** * Load BMP from file using local FileSystem. * Causes GC allocation */ SuperImage loadBMP(string filename) { InputStream input = openForInput(filename); try { ubyte[] data = New!(ubyte[])(cast(size_t)input.size); input.fillArray(data); ArrayStream arrStrm = New!ArrayStream(data); auto img = loadBMP(arrStrm); Delete(arrStrm); Delete(data); return img; } catch (BMPLoadException ex) { throw new Exception("'" ~ filename ~ "' :" ~ ex.msg, ex.file, ex.line, ex.next); } finally { input.close(); } } /** * Load BMP from stream using default image factory. * Causes GC allocation */ SuperImage loadBMP(InputStream istrm) { Compound!(SuperImage, string) res = loadBMP(istrm, defaultImageFactory); if (res[0] is null) throw new BMPLoadException(res[1]); else return res[0]; } /** * Load BMP from stream using specified image factory. * GC-free */ Compound!(SuperImage, string) loadBMP( InputStream istrm, SuperImageFactory imgFac) { SuperImage img = null; BMPFileHeader bmpfh; BMPInfoHeader bmpih; BMPCoreHeader bmpch; BMPOSType osType; uint compression; uint bitsPerPixel; uint redMask, greenMask, blueMask, alphaMask; ubyte[] colormap; int colormapSize; Compound!(SuperImage, string) error(string errorMsg) { if (img) { img.free(); img = null; } if (colormap.length) Delete(colormap); return compound(img, errorMsg); } bmpfh = readStruct!BMPFileHeader(istrm); auto bmphPos = istrm.position; version(BMPDebug) { writefln("bmpfh.type = %s", cast(char[])bmpfh.type); writefln("bmpfh.size = %s", bmpfh.size); writefln("bmpfh.reserved1 = %s", bmpfh.reserved1); writefln("bmpfh.reserved2 = %s", bmpfh.reserved2); writefln("bmpfh.offset = %s", bmpfh.offset); writeln("-------------------"); } if (bmpfh.type != BMPMagic) return error("loadBMP error: input data is not BMP"); uint numChannels = 3; uint width, height; bmpih = readStruct!BMPInfoHeader(istrm); version(BMPDebug) { writefln("bmpih.size = %s", bmpih.size); writefln("bmpih.width = %s", bmpih.width); writefln("bmpih.height = %s", bmpih.height); writefln("bmpih.planes = %s", bmpih.planes); writefln("bmpih.bitsPerPixel = %s", bmpih.bitsPerPixel); writefln("bmpih.compression = %s", bmpih.compression); writefln("bmpih.imageSize = %s", bmpih.imageSize); writefln("bmpih.xPixelsPerMeter = %s", bmpih.xPixelsPerMeter); writefln("bmpih.yPixelsPerMeter = %s", bmpih.yPixelsPerMeter); writefln("bmpih.colorsUsed = %s", bmpih.colorsUsed); writefln("bmpih.colorsImportant = %s", bmpih.colorsImportant); writeln("-------------------"); } if (bmpih.compression > 3) { /* * This is an OS/2 bitmap file, we don't use * bitmap info header but bitmap core header instead */ // We must go back to read bitmap core header istrm.position = bmphPos; bmpch = readStruct!BMPCoreHeader(istrm); osType = BMPOSType.OS2; compression = BMPCompressionType.RGB; bitsPerPixel = bmpch.bitsPerPixel; width = bmpch.width; height = bmpch.height; } else { // Windows style osType = BMPOSType.Win; compression = bmpih.compression; bitsPerPixel = bmpih.bitsPerPixel; width = bmpih.width; height = bmpih.height; } version(BMPDebug) { writefln("osType = %s", [BMPOSType.OS2: "OS/2", BMPOSType.Win: "Windows"][osType]); writefln("width = %s", width); writefln("height = %s", height); writefln("bitsPerPixel = %s", bitsPerPixel); writefln("compression = %s", compression); writeln("-------------------"); } if (bmpih.size >= BMPInfoSize.WIN4 || (compression == BMPCompressionType.BitFields && (bitsPerPixel == 16 || bitsPerPixel == 32))) { bool ok = true; ok = ok && istrm.readLE(&redMask); ok = ok && istrm.readLE(&greenMask); ok = ok && istrm.readLE(&blueMask); version(BMPDebug) { writeln("File has bitfields masks"); writefln("redMask = %#x", redMask); writefln("greenMask = %#x", greenMask); writefln("blueMask = %#x", blueMask); writeln("-------------------"); } if (ok && bmpih.size >= BMPInfoSize.WIN4) { version(BMPDebug) { writeln("File is at least version 4"); } int CSType; int[9] coords; int gammaRed; int gammaGreen; int gammaBlue; ok = ok && istrm.readLE(&alphaMask); ok = ok && istrm.readLE(&CSType); istrm.fillArray(coords); ok = ok && istrm.readLE(&gammaRed); ok = ok && istrm.readLE(&gammaGreen); ok = ok && istrm.readLE(&gammaBlue); if (ok && bmpih.size >= BMPInfoSize.WIN5) { version(BMPDebug) { writeln("File is at least version 5"); } int intent; int profileData; int profileSize; int reserved; ok = ok && istrm.readLE(&intent); ok = ok && istrm.readLE(&profileData); ok = ok && istrm.readLE(&profileSize); ok = ok && istrm.readLE(&reserved); } } if (!ok) return error("loadBMP error: failed to read data of size specified in bmp info structure"); } if (compression != BMPCompressionType.RGB && compression != BMPCompressionType.BitFields && compression != BMPCompressionType.RLE8) return error("loadBMP error: unsupported compression type (RLE4 is not supported yet)"); if (bitsPerPixel != 4 && bitsPerPixel != 8 && bitsPerPixel != 16 && bitsPerPixel != 24 && bitsPerPixel != 32) return error("loadBMP error: unsupported color depth"); uint numberOfColors; ubyte colormapEntrySize = (osType == BMPOSType.OS2)? 3 : 4; ubyte blueShift, greenShift, redShift, alphaShift; ubyte blueScale = 1, greenScale = 1, redScale = 1, alphaScale; if (bitsPerPixel == 8 || bitsPerPixel == 4) { numberOfColors = bmpih.colorsUsed ? bmpih.colorsUsed : (1 << bitsPerPixel); if (numberOfColors == 0 || numberOfColors > 256) return error("loadBMP error: strange number of used colors"); } else if (compression == BMPCompressionType.BitFields && (bitsPerPixel == 16 || bitsPerPixel == 32)) { redShift = calculateShift(redMask); greenShift = calculateShift(greenMask); blueShift = calculateShift(blueMask); alphaShift = calculateShift(alphaMask); version(BMPDebug) { writefln("redShift = %#x", redShift); writefln("greenShift = %#x", greenShift); writefln("blueShift = %#x", blueShift); writefln("alphaShift = %#x", alphaShift); } //scales are used to get equivalent weights for every color channel fit in byte if (calculateDivisor(redMask, redShift) == 0 || calculateDivisor(greenMask, greenShift) == 0 || calculateDivisor(blueMask, blueShift) == 0 || calculateDivisor(alphaMask, alphaShift) == 0) return error("loadBMP error: division by zero when calculating scale"); redScale = calculateScale(redMask, redShift); greenScale = calculateScale(greenMask, greenShift); blueScale = calculateScale(blueMask, blueShift); alphaScale = calculateScale(alphaMask, alphaShift); version(BMPDebug) { writefln("redScale = %#x", redScale); writefln("greenScale = %#x", greenScale); writefln("blueScale = %#x", blueScale); writefln("alphaScale = %#x", alphaScale); } } else if (compression == BMPCompressionType.RGB && (bitsPerPixel == 24 || bitsPerPixel == 32)) { blueMask = 0x000000ff; greenMask = 0x0000ff00; redMask = 0x00ff0000; blueShift = 0; greenShift = 8; redShift = 16; } else if (compression == BMPCompressionType.RGB && bitsPerPixel == 16) { blueMask = 0x001f; greenMask = 0x03e0; redMask = 0x7c00; blueShift = 0; greenShift = 2; redShift = 7; blueScale = 8; } else return error("loadBMP error: unknown compression type / color depth combination"); // Look for palette data if present if (numberOfColors) { colormapSize = numberOfColors * colormapEntrySize; colormap = New!(ubyte[])(colormapSize); istrm.fillArray(colormap); } // Go to begining of pixel data istrm.position = bmpfh.offset; const bool transparent = alphaMask != 0 && compression == BMPCompressionType.BitFields; // Create image img = imgFac.createImage(width, height, transparent ? 4 : 3, 8); enum wrongIndexError = "wrong index for colormap"; if (bitsPerPixel == 4 && compression == BMPCompressionType.RGB) { foreach(y; 0..img.height) { //4 bits per pixel, so width/2 iterations foreach(x; 0..img.width/2) { ubyte[1] buf; istrm.fillArray(buf); const uint first = (buf[0] >> 4)*colormapEntrySize; const uint second = (buf[0] & 0x0f)*colormapEntrySize; if (!checkIndex(first, colormap) || !checkIndex(second, colormap)) return error(wrongIndexError); img[x*2, img.height-y-1] = Color4f(ColorRGBA(colormap[first+2], colormap[first+1], colormap[first])); img[x*2 + 1, img.height-y-1] = Color4f(ColorRGBA(colormap[second+2], colormap[second+1], colormap[second])); } //for odd widths if (img.width & 1) { ubyte[1] buf; istrm.fillArray(buf); const uint index = (buf[0] >> 4)*colormapEntrySize; if (!checkIndex(index, colormap)) return error(wrongIndexError); img[img.width-1, img.height-y-1] = Color4f(ColorRGBA(colormap[index+2], colormap[index+1], colormap[index])); } } } else if (bitsPerPixel == 8 && compression == BMPCompressionType.RGB) { foreach(y; 0..img.height) { foreach(x; 0..img.width) { ubyte[1] buf; istrm.fillArray(buf); const uint index = buf[0]*colormapEntrySize; if (!checkIndex(index, colormap)) return error(wrongIndexError); img[x, img.height-y-1] = Color4f(ColorRGBA(colormap[index+2], colormap[index+1], colormap[index])); } } } else if (bitsPerPixel == 8 && compression == BMPCompressionType.RLE8) { int x, y; while(y < img.height) { ubyte value; if (!istrm.readLE(&value)) break; if (value == 0) { if (!istrm.readLE(&value) || value == 1) break; else { if (value == 0) { x = 0; y++; } else if (value == 2) { version(BMPDebug) writeln("in delta"); ubyte xdelta, ydelta; istrm.readLE(&xdelta); istrm.readLE(&ydelta); x += xdelta; y += ydelta; } else { version(BMPDebug) writeln("in absolute mode"); foreach(i; 0..value) { ubyte j; istrm.readLE(&j); const uint index = j*colormapEntrySize; if (!checkIndex(index, colormap)) return error(wrongIndexError); img[x++, img.height-y-1] = Color4f(ColorRGBA(colormap[index+2], colormap[index+1], colormap[index])); } if (value & 1) { ubyte padding; istrm.readLE(&padding); } } } } else { ubyte j; istrm.readLE(&j); const uint index = j*colormapEntrySize; if (!checkIndex(index, colormap)) return error(wrongIndexError); foreach(i; 0..value) img[x++, img.height-y-1] = Color4f(ColorRGBA(colormap[index+2], colormap[index+1], colormap[index])); } } } else if (bitsPerPixel == 16 || bitsPerPixel == 24 || bitsPerPixel == 32) { const bytesPerPixel = bitsPerPixel / 8; const bytesPerRow = ((bitsPerPixel*width+31)/32)*4; //round to multiple of 4 const bytesPerLine = bytesPerPixel * width; const padding = bytesPerRow - bytesPerLine; if (bitsPerPixel == 24) { foreach(y; 0..img.height) { foreach(x; 0..img.width) { ubyte[3] bgr; istrm.fillArray(bgr); img[x, img.height-y-1] = Color4f(ColorRGBA(bgr[2], bgr[1], bgr[0])); } istrm.seek(padding); } } else if (bitsPerPixel == 16) { foreach(y; 0..img.height) { foreach(x; 0..img.width) { ushort bgr; istrm.readLE(&bgr); const uint p = bgr; const ubyte r = applyMask(p, redMask, redShift, redScale); const ubyte g = applyMask(p, greenMask, greenShift, greenScale); const ubyte b = applyMask(p, blueMask, blueShift, blueScale); img[x, img.height-y-1] = Color4f(ColorRGBA(r,g,b)); } istrm.seek(padding); } } else if (bitsPerPixel == 32) { foreach(y; 0..img.height) { foreach(x; 0..img.width) { uint p; istrm.readLE(&p); const ubyte r = applyMask(p, redMask, redShift, redScale); const ubyte g = applyMask(p, greenMask, greenShift, greenScale); const ubyte b = applyMask(p, blueMask, blueShift, blueScale); img[x, img.height-y-1] = Color4f(ColorRGBA(r, g, b, transparent ? applyMask(p, alphaMask, alphaShift, alphaScale) : 0xff)); } istrm.seek(padding); } } } else return error("loadBMP error: unknown or unsupported compression type / color depth combination"); if (colormap.length) Delete(colormap); return compound(img, ""); } /// unittest { import dlib.core.stream; import std.stdio; SuperImage img; //32 bit with bitfield masks ubyte[] bmpData32 = [ 66, 77, 72, 1, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 56, 0, 0, 0, 8, 0, 0, 0, 8, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 245, 235, 224, 0, 229, 199, 154, 0, 248, 227, 185, 0, 255, 229, 181, 0, 236, 203, 152, 0, 244, 234, 223, 0, 255, 255, 255, 0, 244, 234, 224, 0, 202, 139, 76, 0, 242, 199, 126, 0, 202, 217, 187, 0, 117, 190, 218, 0, 167, 177, 160, 0, 209, 140, 72, 0, 243, 231, 221, 0, 196, 149, 107, 0, 166, 97, 16, 0, 208, 143, 34, 0, 161, 188, 160, 0, 59, 207, 255, 0, 52, 168, 228, 0, 182, 115, 42, 0, 196, 144, 97, 0, 196, 151, 116, 0, 192, 136, 51, 0, 226, 169, 71, 0, 231, 202, 160, 0, 170, 199, 178, 0, 101, 178, 172, 0, 176, 156, 116, 0, 201, 153, 112, 0, 204, 162, 127, 0, 185, 156, 134, 0, 136, 155, 170, 0, 153, 201, 201, 0, 161, 211, 186, 0, 69, 179, 136, 0, 123, 151, 103, 0, 210, 164, 133, 0, 215, 183, 153, 0, 201, 174, 166, 0, 34, 94, 208, 0, 29, 132, 228, 0, 125, 188, 190, 0, 112, 178, 134, 0, 120, 144, 104, 0, 213, 181, 154, 0, 246, 240, 233, 0, 221, 193, 168, 0, 167, 168, 213, 0, 127, 147, 220, 0, 220, 224, 236, 0, 255, 239, 232, 0, 220, 191, 169, 0, 245, 238, 230, 0, 255, 255, 255, 0, 247, 240, 233, 0, 235, 213, 186, 0, 252, 237, 216, 0, 245, 231, 217, 0, 231, 212, 193, 0, 246, 239, 230, 0, 255, 255, 255, 0, 0 ]; auto bmpStream32 = new ArrayStream(bmpData32); img = loadBMP(bmpStream32); assert(img[2,2].convert(8) == Color4(208, 94, 34, 255)); assert(img[5,2].convert(8) == Color4(134, 178, 112, 255)); assert(img[2,5].convert(8) == Color4(34, 143, 208, 255)); assert(img[5,5].convert(8) == Color4(228, 168, 52, 255)); //32 bit with transparency ubyte[] bmpData32_alpha = [ 66, 77, 122, 1, 0, 0, 0, 0, 0, 0, 122, 0, 0, 0, 108, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 1, 0, 32, 0, 3, 0, 0, 0, 0, 1, 0, 0, 109, 11, 0, 0, 109, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 255, 1, 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, 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, 249, 173, 0, 206, 142, 67, 52, 231, 194, 127, 176, 248, 229, 183, 239, 249, 229, 182, 240, 235, 196, 126, 179, 207, 143, 68, 55, 255, 255, 238, 0, 168, 86, 17, 50, 198, 128, 57, 207, 230, 188, 116, 255, 200, 211, 179, 255, 131, 192, 209, 255, 164, 172, 153, 255, 199, 131, 60, 211, 171, 88, 18, 55, 162, 88, 23, 171, 171, 103, 22, 255, 203, 147, 48, 255, 165, 188, 159, 255, 78, 202, 251, 255, 71, 168, 211, 255, 174, 119, 55, 255, 166, 89, 21, 179, 190, 142, 100, 233, 191, 137, 58, 255, 215, 167, 81, 255, 216, 198, 158, 255, 162, 198, 181, 255, 106, 177, 169, 255, 171, 153, 111, 255, 193, 142, 98, 239, 198, 155, 120, 232, 184, 154, 130, 255, 140, 155, 166, 255, 149, 194, 197, 255, 153, 206, 185, 255, 84, 180, 141, 255, 129, 151, 106, 255, 198, 154, 121, 238, 196, 152, 117, 168, 191, 166, 157, 255, 59, 109, 202, 255, 51, 140, 222, 255, 127, 188, 190, 255, 119, 179, 141, 255, 129, 146, 106, 255, 188, 149, 117, 175, 193, 147, 111, 47, 213, 186, 167, 203, 164, 163, 201, 255, 138, 156, 216, 255, 212, 216, 226, 255, 237, 225, 212, 255, 213, 189, 168, 207, 190, 148, 112, 52, 255, 255, 255, 0, 212, 179, 149, 47, 236, 218, 201, 169, 247, 237, 227, 233, 246, 237, 229, 234, 233, 217, 203, 172, 214, 182, 154, 50, 255, 255, 255, 0 ]; auto bmpStream32_alpha = new ArrayStream(bmpData32_alpha); img = loadBMP(bmpStream32_alpha); assert(img[1,1].convert(8) == Color4(167, 186, 213, 203)); assert(img[1,6].convert(8) == Color4(57, 128, 198, 207)); assert(img[2,2].convert(8) == Color4(202, 109, 59, 255)); assert(img[5,5].convert(8) == Color4(211, 168, 71, 255)); //24 bit ubyte[] bmpData24 = [ 66, 77, 248, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 1, 0, 24, 0, 0, 0, 0, 0, 194, 0, 0, 0, 18, 11, 0, 0, 18, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 245, 235, 224, 229, 199, 154, 248, 227, 185, 255, 229, 181, 236, 203, 152, 244, 234, 223, 255, 255, 255, 244, 234, 224, 202, 139, 76, 242, 199, 126, 202, 217, 187, 117, 190, 218, 167, 177, 160, 209, 140, 72, 243, 231, 221, 196, 149, 107, 166, 97, 16, 208, 143, 34, 161, 188, 160, 59, 207, 255, 52, 168, 228, 182, 115, 42, 196, 144, 97, 196, 151, 116, 192, 136, 51, 226, 169, 71, 231, 202, 160, 170, 199, 178, 101, 178, 172, 176, 156, 116, 201, 153, 112, 204, 162, 127, 185, 156, 134, 136, 155, 170, 153, 201, 201, 161, 211, 186, 69, 179, 136, 123, 151, 103, 210, 164, 133, 215, 183, 153, 201, 174, 166, 34, 94, 208, 29, 132, 228, 125, 188, 190, 112, 178, 134, 120, 144, 104, 213, 181, 154, 246, 240, 233, 221, 193, 168, 167, 168, 213, 127, 147, 220, 220, 224, 236, 255, 239, 232, 220, 191, 169, 245, 238, 230, 255, 255, 255, 247, 240, 233, 235, 213, 186, 252, 237, 216, 245, 231, 217, 231, 212, 193, 246, 239, 230, 255, 255, 255, 0, 0 ]; auto bmpStream24 = new ArrayStream(bmpData24); img = loadBMP(bmpStream24); assert(img[2,2].convert(8) == Color4(208, 94, 34, 255)); assert(img[5,5].convert(8) == Color4(228, 168, 52, 255)); //16 bit X1 R5 G5 B5 ubyte[] bmpData16_1_5_5_5 = [ 66, 77, 184, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 1, 0, 16, 0, 0, 0, 0, 0, 130, 0, 0, 0, 18, 11, 0, 0, 18, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 127, 190, 111, 28, 79, 158, 91, 159, 91, 61, 75, 158, 111, 255, 127, 158, 111, 57, 38, 29, 63, 89, 95, 238, 110, 212, 78, 57, 38, 158, 111, 88, 54, 148, 9, 57, 18, 244, 78, 39, 127, 134, 114, 214, 21, 88, 50, 88, 58, 55, 26, 187, 38, 60, 79, 21, 91, 204, 86, 117, 58, 120, 58, 153, 62, 118, 66, 113, 86, 19, 99, 84, 95, 200, 70, 79, 54, 154, 66, 218, 78, 184, 82, 100, 101, 4, 114, 239, 94, 206, 66, 79, 54, 218, 78, 190, 115, 251, 82, 148, 106, 79, 110, 123, 119, 191, 115, 251, 86, 190, 115, 255, 127, 190, 115, 93, 95, 191, 107, 158, 107, 92, 95, 190, 115, 255, 127, 0, 0 ]; auto bmpStream16_1_5_5_5 = new ArrayStream(bmpData16_1_5_5_5); img = loadBMP(bmpStream16_1_5_5_5); /*TODO: pixel comparisons * GIMP shows slightly different pixel values on the same images. */ //16 bit X4 R4 G4 B4 ubyte[] bmpData16_4_4_4_4 = [ 66, 77, 200, 0, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 56, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 1, 0, 16, 0, 3, 0, 0, 0, 130, 0, 0, 0, 18, 11, 0, 0, 18, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 240, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 255, 15, 238, 13, 205, 9, 223, 11, 223, 11, 206, 9, 238, 13, 255, 15, 238, 13, 140, 4, 206, 7, 220, 11, 183, 13, 170, 9, 140, 4, 238, 13, 156, 6, 106, 1, 140, 2, 185, 9, 195, 15, 163, 13, 123, 2, 140, 6, 156, 7, 139, 3, 173, 4, 206, 9, 202, 10, 166, 10, 154, 7, 156, 7, 172, 7, 155, 8, 152, 10, 201, 12, 201, 11, 180, 8, 151, 6, 172, 8, 189, 9, 172, 10, 98, 12, 130, 13, 183, 11, 167, 8, 135, 6, 189, 9, 238, 14, 189, 10, 170, 13, 151, 13, 221, 14, 239, 14, 189, 10, 238, 14, 255, 15, 239, 14, 222, 11, 239, 13, 238, 13, 206, 11, 238, 14, 255, 15, 0, 0 ]; auto bmpStream16_4_4_4_4 = new ArrayStream(bmpData16_4_4_4_4); img = loadBMP(bmpStream16_4_4_4_4); /*TODO: pixel comparisons * GIMP shows slightly different pixel values on the same images. */ //16 bit R5 G6 B5 ubyte[] bmpData16_5_6_5 = [ 66, 77, 200, 0, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 56, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 1, 0, 16, 0, 3, 0, 0, 0, 130, 0, 0, 0, 18, 11, 0, 0, 18, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 0, 0, 224, 7, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 255, 255, 94, 223, 60, 158, 30, 183, 63, 183, 93, 150, 94, 223, 255, 255, 94, 223, 89, 76, 61, 126, 217, 190, 238, 221, 148, 157, 121, 76, 62, 223, 184, 108, 20, 19, 121, 36, 212, 157, 103, 254, 70, 229, 150, 43, 152, 100, 184, 116, 87, 52, 91, 77, 92, 158, 53, 182, 140, 173, 245, 116, 216, 116, 25, 125, 246, 132, 209, 172, 83, 198, 148, 190, 136, 141, 175, 108, 58, 133, 186, 157, 120, 165, 228, 202, 36, 228, 207, 189, 142, 133, 143, 108, 186, 157, 126, 231, 27, 166, 84, 213, 143, 220, 251, 238, 127, 231, 251, 173, 126, 231, 255, 255, 126, 231, 189, 190, 127, 215, 62, 215, 156, 190, 126, 231, 255, 255, 0, 0 ]; auto bmpStream16_5_6_5 = new ArrayStream(bmpData16_5_6_5); img = loadBMP(bmpStream16_5_6_5); /*TODO: pixel comparisons * GIMP shows slightly different pixel values on the same images. */ //4 bit ubyte[] bmpData4 = [ 66, 77, 150, 0, 0, 0, 0, 0, 0, 0, 118, 0, 0, 0, 40, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 1, 0, 4, 0, 0, 0, 0, 0, 32, 0, 0, 0, 196, 14, 0, 0, 196, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 128, 0, 0, 0, 128, 128, 0, 128, 0, 0, 0, 128, 0, 128, 0, 128, 128, 0, 0, 128, 128, 128, 0, 192, 192, 192, 0, 0, 0, 255, 0, 0, 255, 0, 0, 0, 255, 255, 0, 255, 0, 0, 0, 255, 0, 255, 0, 255, 255, 0, 0, 255, 255, 255, 0, 255, 136, 136, 255, 246, 136, 136, 111, 116, 103, 187, 103, 118, 104, 131, 119, 119, 120, 131, 119, 136, 147, 135, 40, 248, 135, 255, 143, 255, 255, 255, 255 ]; auto bmpStream4 = new ArrayStream(bmpData4); img = loadBMP(bmpStream4); assert(img[2,2].convert(8) == Color4(255,0,0,255)); assert(img[1,1].convert(8) == Color4(192,192,192,255)); assert(img[6,2].convert(8) == Color4(0,128,0,255)); } /** * Save BMP to file using local FileSystem. * Causes GC allocation */ void saveBMP(SuperImage img, string filename) { OutputStream output = openForOutput(filename); Compound!(bool, string) res = saveBMP(img, output); output.close(); if (!res[0]) throw new BMPLoadException(res[1]); } /** * Save BMP to stream. * GC-free */ Compound!(bool, string) saveBMP(SuperImage img, OutputStream output) { Compound!(bool, string) error(string errorMsg) { return compound(false, errorMsg); } uint bytesPerRow = (img.width * 24 + 31) / 32 * 4; uint dataOffset = 12 + BMPInfoSize.WIN; uint fileSize = dataOffset + img.height * bytesPerRow; output.writeArray(BMPMagic); output.writeLE(fileSize); output.writeLE(cast(ushort)0); output.writeLE(cast(ushort)0); output.writeLE(dataOffset); output.writeLE(BMPInfoSize.WIN); output.writeLE(img.width); output.writeLE(img.height); output.writeLE(cast(ushort)1); output.writeLE(cast(ushort)24); output.writeLE(BMPCompressionType.RGB); output.writeLE(bytesPerRow * img.height); output.writeLE(2834); output.writeLE(2834); output.writeLE(0); output.writeLE(0); foreach_reverse(y; 0..img.height) { foreach(x; 0..img.width) { ubyte[3] rgb; ColorRGBA color = img[x, y].convert(8); rgb[0] = cast(ubyte)color[2]; rgb[1] = cast(ubyte)color[1]; rgb[2] = cast(ubyte)color[0]; output.writeArray(rgb); } //padding for(uint i=0; i<(bytesPerRow-img.width*3); ++i) { output.writeLE(cast(ubyte)0); } } return compound(true, ""); } ================================================ FILE: dlib/image/io/hdr.d ================================================ /* Copyright (c) 2016-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Decode and encode Radiance HDR/RGBE images * * Copyright: Timur Gafarov 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.io.hdr; import std.stdio; import std.math; import dlib.core.memory; import dlib.core.stream; import dlib.core.compound; import dlib.container.array; import dlib.filesystem.local; import dlib.image.color; import dlib.image.image; import dlib.image.hdri; import dlib.image.io; import dlib.math.utils; struct ColorRGBE { ubyte r; ubyte g; ubyte b; ubyte e; } ColorRGBE floatToRGBE(Color4f c) { ColorRGBE rgbe; float v = c.r; if (c.g > v) v = c.g; if (c.b > v) v = c.b; if (v < EPSILON) { rgbe.r = 0; rgbe.g = 0; rgbe.b = 0; rgbe.e = 0; } else { int e; v = frexp(v, e) * 256.0f / v; rgbe.r = cast(ubyte)(c.r * v); rgbe.g = cast(ubyte)(c.g * v); rgbe.b = cast(ubyte)(c.b * v); rgbe.e = cast(ubyte)(e + 128); } return rgbe; } void readLineFromStream(InputStream istrm, ref Array!char line) { char c; do { if (istrm.readable) istrm.readBytes(&c, 1); else break; if (c != '\n') line.append(c); } while(c != '\n'); } class HDRLoadException: ImageLoadException { this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { super(msg, file, line, next); } } /** * Load HDR from file using local FileSystem. * Causes GC allocation */ SuperHDRImage loadHDR(string filename) { InputStream input = openForInput(filename); ubyte[] data = New!(ubyte[])(cast(size_t)input.size); input.fillArray(data); ArrayStream arrStrm = New!ArrayStream(data); auto img = loadHDR(arrStrm); Delete(arrStrm); Delete(data); input.close(); return img; } /** * Load HDR from stream using default image factory. * Causes GC allocation */ SuperHDRImage loadHDR(InputStream istrm) { Compound!(SuperHDRImage, string) res = loadHDR(istrm, defaultHDRImageFactory); if (res[0] is null) throw new HDRLoadException(res[1]); else return res[0]; } /** * Load HDR from stream using specified image factory. * GC-free */ Compound!(SuperHDRImage, string) loadHDR( InputStream istrm, SuperHDRImageFactory imgFac) { SuperHDRImage img = null; Compound!(SuperHDRImage, string) error(string errorMsg) { if (img) { img.free(); img = null; } return compound(img, errorMsg); } char[11] magic; istrm.fillArray(magic); if (magic != "#?RADIANCE\n") { if (magic[0..7] == "#?RGBE\n") { istrm.position = 7; } else return error("loadHDR error: signature check failed"); } // Read header Array!char line; do { line.free(); readLineFromStream(istrm, line); // TODO: parse assignments } while (line.length); // Read resolution line line.free(); readLineFromStream(istrm, line); char xsign, ysign; uint width, height; int count = sscanf(line.data.ptr, "%cY %u %cX %u", &ysign, &height, &xsign, &width); if (count != 4) return error("loadHDR error: invalid resolution line"); // Read pixel data ubyte[] dataRGBE = New!(ubyte[])(width * height * 4); ubyte[4] col; for (uint y = 0; y < height; y++) { istrm.readBytes(col.ptr, 4); //Header of 0x2, 0x2 is new Radiance RLE scheme if (col[0] == 2 && col[1] == 2 && col[2] >= 0) { // Each channel is run length encoded seperately for (uint chi = 0; chi < 4; chi++) { uint x = 0; while (x < width) { uint start = (y * width + x) * 4; ubyte num = 0; istrm.readBytes(&num, 1); if (num <= 128) // No run, just read the values { for (uint i = 0; i < num; i++) { ubyte value; istrm.readBytes(&value, 1); dataRGBE[start + chi + i*4] = value; } } else // We have a run, so get the value and set all the values for this run { ubyte value; istrm.readBytes(&value, 1); num -= 128; for (uint i = 0; i < num; i++) { dataRGBE[start + chi + i*4] = value; } } x += num; } } } else // Old Radiance RLE scheme { for (uint x = 0; x < width; x++) { if (x > 0) istrm.readBytes(col.ptr, 4); uint prev = (y * width + x - 1) * 4; uint start = (y * width + x) * 4; // Check for the RLE header for this scanline if (col[0] == 1 && col[1] == 1 && col[2] == 1) { // Do the run int num = (cast(int)col[3]) & 0xFF; ubyte r = dataRGBE[prev]; ubyte g = dataRGBE[prev + 1]; ubyte b = dataRGBE[prev + 2]; ubyte e = dataRGBE[prev + 3]; for (uint i = 0; i < num; i++) { dataRGBE[start + i*4 + 0] = r; dataRGBE[start + i*4 + 1] = g; dataRGBE[start + i*4 + 2] = b; dataRGBE[start + i*4 + 3] = e; } x += num-1; } else // No runs here, just read the data { dataRGBE[start] = col[0]; dataRGBE[start + 1] = col[1]; dataRGBE[start + 2] = col[2]; dataRGBE[start + 3] = col[3]; } } } } // Convert RGBE to IEEE floats img = imgFac.createImage(width, height); foreach(y; 0..height) foreach(x; 0..width) { size_t start = (width * y + x) * 4; ubyte exponent = dataRGBE[start + 3]; if (exponent == 0) { img[x, y] = Color4f(0, 0, 0, 1); } else { float v = ldexp(1.0, cast(int)exponent - (128 + 8)); float r = cast(float)(dataRGBE[start]) * v; float g = cast(float)(dataRGBE[start + 1]) * v; float b = cast(float)(dataRGBE[start + 2]) * v; img[x, y] = Color4f(r, g, b, 1); } } Delete(dataRGBE); return compound(img, ""); } /** * Save HDR to file using local FileSystem. * Causes GC allocation */ void saveHDR(SuperHDRImage img, string filename) { OutputStream output = openForOutput(filename); Compound!(bool, string) res = saveHDR(img, output); output.close(); } /** * Save HDR to stream. * GC-free */ Compound!(bool, string) saveHDR(SuperHDRImage img, OutputStream output) { Compound!(bool, string) error(string errorMsg) { return compound(false, errorMsg); } // Signature and header string hdrStart = "#?RADIANCE\n\n"; // double LF needed to mark end of header output.writeArray(hdrStart); // Resolution line char[256] resolution; int len = sprintf(resolution.ptr, "-Y %d +X %d\n", img.height, img.width); output.writeArray(resolution[0..len]); ubyte[] scanline = New!(ubyte[])(img.width * 4); for (uint y = 0; y < img.height; y++) { ubyte[4] scanlineHeader; scanlineHeader[0] = 2; scanlineHeader[1] = 2; scanlineHeader[2] = cast(ubyte)(img.width >> 8); scanlineHeader[3] = cast(ubyte)(img.width & 0xFF); output.writeArray(scanlineHeader); // Convert a scanline to RGBE decomposing channels for (uint x = 0; x < img.width; x++) { ColorRGBE rgbe = img[x, y].floatToRGBE; scanline[x] = rgbe.r; scanline[x + img.width] = rgbe.g; scanline[x + img.width * 2] = rgbe.b; scanline[x + img.width * 3] = rgbe.e; } // Write channels foreach(ch; 0..4) { uint offset = ch * img.width; writeBufferRLE(output, scanline[offset..offset+img.width]); } } Delete(scanline); return compound(true, ""); } /* * Based on code by Bruce Walter: * http://www.graphics.cornell.edu/~bjw/rgbe/rgbe.c */ void writeBufferRLE(OutputStream output, ubyte[] data) { enum MINRUNLENGTH = 4; int cur, beg_run, run_count, old_run_count, nonrun_count; ubyte[2] buf; cur = 0; while(cur < data.length) { beg_run = cur; // find next run of length at least 4 if one exists run_count = old_run_count = 0; while((run_count < MINRUNLENGTH) && (beg_run < data.length)) { beg_run += run_count; old_run_count = run_count; run_count = 1; while((beg_run + run_count < data.length) && (run_count < 127) && (data[beg_run] == data[beg_run + run_count])) run_count++; } // if data before next big run is a short run then write it as such if ((old_run_count > 1) && (old_run_count == beg_run - cur)) { buf[0] = cast(ubyte)(128 + old_run_count); // write short run buf[1] = data[cur]; output.writeArray(buf); cur = beg_run; } // write out bytes until we reach the start of the next run while(cur < beg_run) { nonrun_count = beg_run - cur; if (nonrun_count > 128) nonrun_count = 128; buf[0] = cast(ubyte)nonrun_count; output.writeBytes(buf.ptr, 1); output.writeBytes(&data[cur], nonrun_count); cur += nonrun_count; } // write out next run if one was found if (run_count >= MINRUNLENGTH) { buf[0] = cast(ubyte)(128 + run_count); buf[1] = data[beg_run]; output.writeArray(buf); cur += run_count; } } } ================================================ FILE: dlib/image/io/jpeg.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Decode JPEG images * * Description: * Simple self-contained JPEG decoder, supports only baseline format. * * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.io.jpeg; import std.stdio; import std.algorithm; import std.string; import std.traits; import std.math; import dlib.core.memory; import dlib.core.stream; import dlib.core.compound; import dlib.core.bitio; import dlib.container.array; import dlib.filesystem.local; import dlib.image.color; import dlib.image.image; import dlib.math.utils; // Uncomment this to see debug messages //version = JPEGDebug; T readNumeric(T) (InputStream istrm, Endian endian = Endian.Little) if (is(T == ubyte)) { ubyte b; istrm.readBytes(&b, 1); return b; } T readNumeric(T) (InputStream istrm, Endian endian = Endian.Little) if (is(T == ushort)) { union U16 { ubyte[2] asBytes; ushort asUshort; } U16 u16; istrm.readBytes(u16.asBytes.ptr, 2); version(LittleEndian) { if (endian == Endian.Big) return u16.asUshort.swapEndian16; else return u16.asUshort; } else { if (endian == Endian.Little) return u16.asUshort.swapEndian16; else return u16.asUshort; } } char[size] readChars(size_t size) (InputStream istrm) { char[size] chars; istrm.readBytes(chars.ptr, size); return chars; } /* * JPEG-related Huffman coding */ struct HuffmanCode { ushort bits; ushort length; auto bitString() { return .bitString(bits, length); } } struct HuffmanTableEntry { HuffmanCode code; ubyte value; } Array!char bitString(T)(T n, uint len = 1) if (isIntegral!T) { Array!char arr; const int size = T.sizeof * 8; bool s = 0; for (int a = 0; a < size; a++) { bool bit = n >> (size - 1); if (bit) s = 1; if (s) { arr.append(bit + '0'); } n <<= 1; } while (arr.length < len) arr.appendLeft('0'); return arr; } struct HuffmanTreeNode { HuffmanTreeNode* parent; HuffmanTreeNode* left; HuffmanTreeNode* right; ubyte ch; uint freq; bool blank = true; this( HuffmanTreeNode* leftNode, HuffmanTreeNode* rightNode, ubyte symbol, uint frequency, bool isBlank) { parent = null; left = leftNode; right = rightNode; if (left !is null) left.parent = &this; if (right !is null) right.parent = &this; ch = symbol; freq = frequency; blank = isBlank; } bool isLeaf() { return (left is null && right is null); } void free() { if (left !is null) { left.free(); Delete(left); } if (right !is null) { right.free(); Delete(right); } } } HuffmanTreeNode* emptyNode() { return New!HuffmanTreeNode(null, null, cast(ubyte)0, 0, false); } HuffmanTreeNode* treeFromTable(Array!(HuffmanTableEntry) table) { HuffmanTreeNode* root = emptyNode(); foreach(i, v; table.data) treeAddCode(root, v.code, v.value); return root; } void treeAddCode(HuffmanTreeNode* root, HuffmanCode code, ubyte value) { HuffmanTreeNode* node = root; auto bs = code.bitString; foreach(bit; bs.data) { if (bit == '0') { if (node.left is null) { node.left = emptyNode(); node.left.parent = node; } node = node.left; } else if (bit == '1') { if (node.right is null) { node.right = emptyNode(); node.right.parent = node; } node = node.right; } } assert (node !is null); node.ch = value; bs.free(); } /* * JPEG-related data types */ enum JPEGMarkerType { Unknown, SOI, SOF0, SOF1, SOF2, DHT, DQT, DRI, SOS, RSTn, APP0, APPn, COM, EOI } struct JPEGImage { struct JFIF { ubyte versionMajor; ubyte versionMinor; ubyte units; ushort xDensity; ushort yDensity; ubyte thumbnailWidth; ubyte thumbnailHeight; ubyte[] thumbnail; void free() { if (thumbnail.length) Delete(thumbnail); } } struct DQT { ubyte precision; ubyte tableId; ubyte[] table; void free() { if (table.length) Delete(table); } } struct FrameComponent { ubyte hSubsampling; ubyte vSubsampling; ubyte dqtTableId; } struct Frame { ubyte precision; ushort height; ushort width; ubyte componentsNum; FrameComponent[] components; bool isProgressive; void free() { if (components.length) Delete(components); } } struct DHT { ubyte clas; ubyte tableId; Array!HuffmanTableEntry huffmanTable; HuffmanTreeNode* huffmanTree; void free() { huffmanTree.free(); Delete(huffmanTree); huffmanTable.free(); } } struct ScanComponent { ubyte tableIdDC; ubyte tableIdAC; } struct Scan { ubyte componentsNum; ScanComponent[] components; ubyte spectralSelectionStart; ubyte spectralSelectionEnd; ubyte successiveApproximationBitHigh; ubyte successiveApproximationBitLow; void free() { if (components.length) Delete(components); } } JFIF jfif; DQT[] dqt; Frame frame; DHT[] dht; // TODO: multiple scans for progressive JPEG support Scan scan; DQT* addDQT() { if (dqt.length > 0) reallocateArray(dqt, dqt.length+1); else dqt = New!(DQT[])(1); return &dqt[$-1]; } DHT* addDHT() { if (dht.length > 0) reallocateArray(dht, dht.length+1); else dht = New!(DHT[])(1); return &dht[$-1]; } void free() { jfif.free(); foreach(ref t; dqt) t.free(); if (dqt.length) Delete(dqt); frame.free(); foreach(ref t; dht) t.free(); if (dht.length) Delete(dht); scan.free(); } DQT* getQuantizationTable(ubyte id) { foreach(ref t; dqt) if (t.tableId == id) return &t; return null; } DHT* getHuffmanTable(ubyte clas, ubyte id) { foreach(ref t; dht) if (t.clas == clas && t.tableId == id) return &t; return null; } } /** * Load JPEG from file using local FileSystem. * Causes GC allocation */ SuperImage loadJPEG(string filename) { InputStream input = openForInput(filename); ubyte[] data = New!(ubyte[])(cast(size_t)input.size); input.fillArray(data); ArrayStream arrStrm = New!ArrayStream(data); auto img = loadJPEG(arrStrm); Delete(arrStrm); Delete(data); input.close(); return img; } /** * Load JPEG from stream using default image factory. * Causes GC allocation */ SuperImage loadJPEG(InputStream istrm) { Compound!(SuperImage, string) res = loadJPEG(istrm, defaultImageFactory); if (res[0] is null) throw new Exception(res[1]); else return res[0]; } /** * Load JPEG from stream using specified image factory. * GC-free */ Compound!(SuperImage, string) loadJPEG( InputStream istrm, SuperImageFactory imgFac) { JPEGImage jpg; SuperImage img = null; while (istrm.readable) { JPEGMarkerType mt; auto res = readMarker(&jpg, istrm, &mt); if (res[0]) { // TODO: add progressive JPEG support if (mt == JPEGMarkerType.SOF2) { jpg.free(); return compound(img, "loadJPEG error: progressive JPEG is not supported"); } else if (mt == JPEGMarkerType.SOS) break; } else { jpg.free(); return compound(img, res[1]); } } auto res = decodeScanData(&jpg, istrm, imgFac); jpg.free(); return res; } /* * Decode marker from JPEG stream */ Compound!(bool, string) readMarker( JPEGImage* jpg, InputStream istrm, JPEGMarkerType* mt) { ushort magic = istrm.readNumeric!ushort(Endian.Big); switch (magic) { case 0xFFD8: // Start of image *mt = JPEGMarkerType.SOI; version(JPEGDebug) writeln("SOI"); break; case 0xFFE0: // JFIF data *mt = JPEGMarkerType.APP0; return readJFIF(jpg, istrm); case 0xFFE1: // Application-specific data *mt = JPEGMarkerType.APPn; return readAPPn(jpg, istrm, 1); case 0xFFE2: // Application-specific data *mt = JPEGMarkerType.APPn; return readAPPn(jpg, istrm, 2); case 0xFFE3: // Application-specific data *mt = JPEGMarkerType.APPn; return readAPPn(jpg, istrm, 3); case 0xFFE4: // Application-specific data *mt = JPEGMarkerType.APPn; return readAPPn(jpg, istrm, 4); case 0xFFE5: // Application-specific data *mt = JPEGMarkerType.APPn; return readAPPn(jpg, istrm, 5); case 0xFFE6: // Application-specific data *mt = JPEGMarkerType.APPn; return readAPPn(jpg, istrm, 6); case 0xFFE7: // Application-specific data *mt = JPEGMarkerType.APPn; return readAPPn(jpg, istrm, 7); case 0xFFE8: // Application-specific data *mt = JPEGMarkerType.APPn; return readAPPn(jpg, istrm, 8); case 0xFFE9: // Application-specific data *mt = JPEGMarkerType.APPn; return readAPPn(jpg, istrm, 9); case 0xFFEA: // Application-specific data *mt = JPEGMarkerType.APPn; return readAPPn(jpg, istrm, 10); case 0xFFEB: // Application-specific data *mt = JPEGMarkerType.APPn; return readAPPn(jpg, istrm, 11); case 0xFFEC: // Application-specific data *mt = JPEGMarkerType.APPn; return readAPPn(jpg, istrm, 12); case 0xFFED: // Application-specific data *mt = JPEGMarkerType.APPn; return readAPPn(jpg, istrm, 13); case 0xFFEE: // Application-specific data *mt = JPEGMarkerType.APPn; return readAPPn(jpg, istrm, 14); case 0xFFEF: // Application-specific data *mt = JPEGMarkerType.APPn; return readAPPn(jpg, istrm, 15); case 0xFFDB: // Quantization table(s) *mt = JPEGMarkerType.DQT; return readDQT(jpg, istrm); case 0xFFC0: // Start of frame (baseline) *mt = JPEGMarkerType.SOF0; return readSOF0(jpg, istrm); case 0xFFC1: // Start of frame (extended sequential) *mt = JPEGMarkerType.SOF1; version(JPEGDebug) writeln("SOF1"); break; case 0xFFC2: // Start of frame (progressive) *mt = JPEGMarkerType.SOF2; return readSOF2(jpg, istrm); case 0xFFC4: // Huffman table(s) *mt = JPEGMarkerType.DHT; return readDHT(jpg, istrm); case 0xFFDA: // Start of scan *mt = JPEGMarkerType.SOS; return readSOS(jpg, istrm); case 0xFFFE: // Comments *mt = JPEGMarkerType.COM; return readCOM(jpg, istrm); case 0xFFDD: // Restart interval // TODO *mt = JPEGMarkerType.DRI; version(JPEGDebug) writeln("DRI"); break; case 0xFFD0: // Restart // TODO *mt = JPEGMarkerType.RSTn; version(JPEGDebug) writeln("RST0"); break; case 0xFFD1: // Restart // TODO *mt = JPEGMarkerType.RSTn; version(JPEGDebug) writeln("RST1"); break; case 0xFFD2: // Restart // TODO *mt = JPEGMarkerType.RSTn; version(JPEGDebug) writeln("RST2"); break; case 0xFFD3: // Restart // TODO *mt = JPEGMarkerType.RSTn; version(JPEGDebug) writeln("RST3"); break; case 0xFFD4: // Restart // TODO *mt = JPEGMarkerType.RSTn; version(JPEGDebug) writeln("RST4"); break; case 0xFFD5: // Restart // TODO *mt = JPEGMarkerType.RSTn; version(JPEGDebug) writeln("RST5"); break; case 0xFFD6: // Restart // TODO *mt = JPEGMarkerType.RSTn; version(JPEGDebug) writeln("RST6"); break; case 0xFFD7: // Restart // TODO *mt = JPEGMarkerType.RSTn; version(JPEGDebug) writeln("RST7"); break; case 0xFFD9: // End of image // TODO *mt = JPEGMarkerType.EOI; version(JPEGDebug) writeln("EOI"); break; default: *mt = JPEGMarkerType.Unknown; break; } return compound(true, ""); } Compound!(bool, string) readJFIF(JPEGImage* jpg, InputStream istrm) { ushort jfif_length = istrm.readNumeric!ushort(Endian.Big); char[5] jfif_id = istrm.readChars!5; if (jfif_id != "JFIF\0") return compound(false, "loadJPEG error: illegal JFIF header"); jpg.jfif.versionMajor = istrm.readNumeric!ubyte; jpg.jfif.versionMinor = istrm.readNumeric!ubyte; jpg.jfif.units = istrm.readNumeric!ubyte; jpg.jfif.xDensity = istrm.readNumeric!ushort(Endian.Big); jpg.jfif.yDensity = istrm.readNumeric!ushort(Endian.Big); jpg.jfif.thumbnailWidth = istrm.readNumeric!ubyte; jpg.jfif.thumbnailHeight = istrm.readNumeric!ubyte; uint jfif_thumb_length = jpg.jfif.thumbnailWidth * jpg.jfif.thumbnailHeight * 3; if (jfif_thumb_length > 0) { jpg.jfif.thumbnail = New!(ubyte[])(jfif_thumb_length); istrm.readBytes(jpg.jfif.thumbnail.ptr, jfif_thumb_length); } version(JPEGDebug) { writefln("APP0/JFIF length: %s", jfif_length); writefln("APP0/JFIF identifier: %s", jfif_id); writefln("APP0/JFIF version major: %s", jpg.jfif.versionMajor); writefln("APP0/JFIF version minor: %s", jpg.jfif.versionMinor); writefln("APP0/JFIF units: %s", jpg.jfif.units); writefln("APP0/JFIF xdensity: %s", jpg.jfif.xDensity); writefln("APP0/JFIF ydensity: %s", jpg.jfif.yDensity); writefln("APP0/JFIF xthumbnail: %s", jpg.jfif.thumbnailWidth); writefln("APP0/JFIF ythumbnail: %s", jpg.jfif.thumbnailHeight); } return compound(true, ""); } /* * APP1 - EXIF, XMP, ExtendedXMP, QVCI, FLIR * APP2 - ICC, FPXR, MPF, PreviewImage * APP3 - Kodak Meta, Stim, PreviewImage * APP4 - Scalado, FPXR, PreviewImage * APP5 - RMETA, PreviewImage * APP6 - EPPIM, NITF, HP TDHD * APP7 - Pentax, Qualcomm * APP8 - SPIFF * APP9 - MediaJukebox * APP10 - PhotoStudio comment * APP11 - JPEG-HDR * APP12 - PictureInfo, Ducky * APP13 - Photoshop, Adobe CM * APP14 - Adobe * APP15 - GraphicConverter */ Compound!(bool, string) readAPPn(JPEGImage* jpg, InputStream istrm, uint n) { ushort app_length = istrm.readNumeric!ushort(Endian.Big); ubyte[] app = New!(ubyte[])(app_length-2); istrm.readBytes(app.ptr, app_length-2); // TODO: interpret APP data (EXIF etc.) and save it somewhere. // Maybe add a generic ImageInfo object for this? Delete(app); version(JPEGDebug) { writefln("APP%s length: %s", n, app_length); } return compound(true, ""); } Compound!(bool, string) readCOM(JPEGImage* jpg, InputStream istrm) { ushort com_length = istrm.readNumeric!ushort(Endian.Big); ubyte[] com = New!(ubyte[])(com_length-2); istrm.readBytes(com.ptr, com_length-2); version(JPEGDebug) { writefln("COM string: \"%s\"", cast(string)com); writefln("COM length: %s", com_length); } // TODO: save COM data somewhere. // Maybe add a generic ImageInfo object for this? Delete(com); return compound(true, ""); } Compound!(bool, string) readDQT(JPEGImage* jpg, InputStream istrm) { ushort dqt_length = istrm.readNumeric!ushort(Endian.Big); version(JPEGDebug) { writefln("DQT length: %s", dqt_length); } dqt_length -= 2; while(dqt_length) { JPEGImage.DQT* dqt = jpg.addDQT(); ubyte bite = istrm.readNumeric!ubyte; dqt.precision = bite.hiNibble; dqt.tableId = bite.loNibble; dqt_length--; if (dqt.precision == 0) { dqt.table = New!(ubyte[])(64); dqt_length -= 64; } else if (dqt.precision == 1) { dqt.table = New!(ubyte[])(128); dqt_length -= 128; } istrm.readBytes(dqt.table.ptr, dqt.table.length); version(JPEGDebug) { writefln("DQT precision: %s", dqt.precision); writefln("DQT table id: %s", dqt.tableId); writefln("DQT table: %s", dqt.table); } } return compound(true, ""); } Compound!(bool, string) readSOF0(JPEGImage* jpg, InputStream istrm) { ushort sof0_length = istrm.readNumeric!ushort(Endian.Big); jpg.frame.isProgressive = false; jpg.frame.precision = istrm.readNumeric!ubyte; jpg.frame.height = istrm.readNumeric!ushort(Endian.Big); jpg.frame.width = istrm.readNumeric!ushort(Endian.Big); jpg.frame.componentsNum = istrm.readNumeric!ubyte; version(JPEGDebug) { writefln("SOF length: %s", sof0_length); writefln("SOF precision: %s", jpg.frame.precision); writefln("SOF height: %s", jpg.frame.height); writefln("SOF width: %s", jpg.frame.width); writefln("SOF components: %s", jpg.frame.componentsNum); } jpg.frame.components = New!(JPEGImage.FrameComponent[])(jpg.frame.componentsNum); foreach(ref c; jpg.frame.components) { ubyte c_id = istrm.readNumeric!ubyte; ubyte bite = istrm.readNumeric!ubyte; c.hSubsampling = bite.hiNibble; c.vSubsampling = bite.loNibble; c.dqtTableId = istrm.readNumeric!ubyte; version(JPEGDebug) { writefln("SOF component id: %s", c_id); writefln("SOF component %s hsubsampling: %s", c_id, c.hSubsampling); writefln("SOF component %s vsubsampling: %s", c_id, c.vSubsampling); writefln("SOF component %s table id: %s", c_id, c.dqtTableId); } } return compound(true, ""); } Compound!(bool, string) readSOF2(JPEGImage* jpg, InputStream istrm) { auto res = readSOF0(jpg, istrm); jpg.frame.isProgressive = true; return res; } Compound!(bool, string) readDHT(JPEGImage* jpg, InputStream istrm) { ushort dht_length = istrm.readNumeric!ushort(Endian.Big); version(JPEGDebug) { writefln("DHT length: %s", dht_length); } dht_length -= 2; while(dht_length > 0) { JPEGImage.DHT* dht = jpg.addDHT(); ubyte bite = istrm.readNumeric!ubyte; dht_length--; dht.clas = bite.hiNibble; dht.tableId = bite.loNibble; ubyte[16] dht_code_lengths; istrm.readBytes(dht_code_lengths.ptr, 16); dht_length -= 16; version(JPEGDebug) { writefln("DHT class: %s (%s)", dht.clas, dht.clas? "AC":"DC"); writefln("DHT tableId: %s", dht.tableId); writefln("DHT Huffman code lengths: %s", dht_code_lengths); } // Read Huffman table int totalCodes = reduce!("a + b")(0, dht_code_lengths); int storedCodes = 0; ubyte treeLevel = 0; ushort bits = 0; while (storedCodes != totalCodes) { while (treeLevel < 15 && dht_code_lengths[treeLevel] == 0) { treeLevel++; bits *= 2; } if (treeLevel < 16) { uint bitsNum = treeLevel + 1; HuffmanCode code = HuffmanCode(bits, cast(ushort)bitsNum); auto entry = HuffmanTableEntry(code, istrm.readNumeric!ubyte); dht.huffmanTable.append(entry); dht_length--; storedCodes++; bits++; dht_code_lengths[treeLevel]--; } } dht.huffmanTree = treeFromTable(dht.huffmanTable); } return compound(true, ""); } Compound!(bool, string) readSOS(JPEGImage* jpg, InputStream istrm) { ushort sos_length = istrm.readNumeric!ushort(Endian.Big); jpg.scan.componentsNum = istrm.readNumeric!ubyte; version(JPEGDebug) { writefln("SOS length: %s", sos_length); writefln("SOS components: %s", jpg.scan.componentsNum); } jpg.scan.components = New!(JPEGImage.ScanComponent[])(jpg.scan.componentsNum); foreach(ref c; jpg.scan.components) { ubyte c_id = istrm.readNumeric!ubyte; ubyte bite = istrm.readNumeric!ubyte; c.tableIdDC = bite.hiNibble; c.tableIdAC = bite.loNibble; version(JPEGDebug) { writefln("SOS component id: %s", c_id); writefln("SOS component %s DC table id: %s", c_id, c.tableIdDC); writefln("SOS component %s AC table id: %s", c_id, c.tableIdAC); } } jpg.scan.spectralSelectionStart = istrm.readNumeric!ubyte; jpg.scan.spectralSelectionEnd = istrm.readNumeric!ubyte; ubyte bite = istrm.readNumeric!ubyte; jpg.scan.successiveApproximationBitHigh = bite.hiNibble; jpg.scan.successiveApproximationBitLow = bite.loNibble; version(JPEGDebug) { writefln("SOS spectral selection start: %s", jpg.scan.spectralSelectionStart); writefln("SOS spectral selection end: %s", jpg.scan.spectralSelectionEnd); writefln("SOS successive approximation bit: %s", jpg.scan.successiveApproximationBitHigh); writefln("SOS successive approximation bit low: %s", jpg.scan.successiveApproximationBitLow); } return compound(true, ""); } struct ScanBitStream { InputStream istrm; bool endMarkerFound = false; uint bytesRead = 0; ubyte prevByte = 0x00; ubyte curByte = 0x00; ubyte readNextByte() { ubyte b = istrm.readNumeric!ubyte; bytesRead++; endMarkerFound = (prevByte == 0xFF && b == 0xD9); assert(!endMarkerFound); if (!endMarkerFound) { prevByte = b; curByte = b; return b; } else { curByte = 0; } return curByte; } bool readable() { return !istrm.readable || endMarkerFound; } uint bitPos = 0; // Huffman decode a byte Compound!(bool, string) decodeByte(HuffmanTreeNode* node, ubyte* result) { while(!node.isLeaf) { ubyte b = curByte; bool bit = getBit(b, 7-bitPos); bitPos++; if (bitPos == 8) { bitPos = 0; readNextByte(); if (b == 0xFF) { b = curByte; if (b == 0x00) { readNextByte(); } } } if (bit) node = node.right; else node = node.left; if (node is null) return compound(false, "loadJPEG error: no Huffman code found"); } *result = node.ch; return compound(true, ""); } // Read len bits from stream to buffer uint readBits(ubyte len) { uint buffer = 0; uint i = 0; uint by = 0; uint bi = 0; while (i < len) { ubyte b = curByte; bool bit = getBit(b, 7-bitPos); buffer = setBit(buffer, (by * 8 + bi), bit); bi++; if (bi == 8) { bi = 0; by++; } i++; bitPos++; if (bitPos == 8) { bitPos = 0; readNextByte(); if (b == 0xFF) { b = curByte; if (b == 0x00) readNextByte(); } } } return buffer; } } /* * Decodes compressed data and creates RGB image from it */ Compound!(SuperImage, string) decodeScanData( JPEGImage* jpg, InputStream istrm, SuperImageFactory imgFac) { SuperImage img = imgFac.createImage(jpg.frame.width, jpg.frame.height, 3, 8); MCU mcu; foreach(ci, ref c; jpg.frame.components) { if (ci == 0) mcu.createYBlocks(c.hSubsampling, c.vSubsampling); else if (ci == 1) mcu.createCbBlocks(c.hSubsampling, c.vSubsampling); else if (ci == 2) mcu.createCrBlocks(c.hSubsampling, c.vSubsampling); } Compound!(SuperImage, string) error(string errorMsg) { mcu.free(); if (img) { img.free(); img = null; } return compound(img, errorMsg); } // Decode DCT coefficient from bit buffer int decodeCoef(uint buffer, ubyte numBits) { bool positive = getBit(buffer, 0); int value = 0; foreach(j; 0..numBits) { bool bit = getBit(buffer, numBits-1-j); value = setBit(value, j, bit); } if (positive) return value; else return value - 2^^numBits + 1; } static const ubyte[64] dezigzag = [ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 ]; if (jpg.scan.componentsNum != 3) { return error(format( "loadJPEG error: unsupported number of scan components: %s", jpg.scan.componentsNum)); } // Store previous DC coefficients int[3] dcCoefPrev; if (jpg.dqt.length == 0) return error("loadJPEG error: no DQTs found"); ScanBitStream sbs; sbs.endMarkerFound = false; sbs.bytesRead = 0; sbs.prevByte = 0x00; sbs.curByte = 0x00; sbs.istrm = istrm; sbs.readNextByte(); uint numMCUsH = jpg.frame.width / mcu.width + ((jpg.frame.width % mcu.width) > 0); uint numMCUsV = jpg.frame.height / mcu.height + ((jpg.frame.height % mcu.height) > 0); // Read MCUs foreach(mcuY; 0..numMCUsV) foreach(mcuX; 0..numMCUsH) { // Read MCU for each channel foreach(ci, ref c; jpg.scan.components) { auto tableDC = jpg.getHuffmanTable(0, c.tableIdDC); auto tableAC = jpg.getHuffmanTable(1, c.tableIdAC); if (tableDC is null) return error("loadJPEG error: illegal DC table index in MCU component"); if (tableAC is null) return error("loadJPEG error: illegal AC table index in MCU component"); auto component = jpg.frame.components[ci]; auto hblocks = component.hSubsampling; auto vblocks = component.vSubsampling; auto dqtTableId = component.dqtTableId; if (dqtTableId >= jpg.dqt.length) return error("loadJPEG error: illegal DQT table index in MCU component"); // Read 8x8 blocks foreach(by; 0..vblocks) foreach(bx; 0..hblocks) { int[8*8] block; // Read DC coefficient ubyte dcDiffLen; auto res = sbs.decodeByte(tableDC.huffmanTree, &dcDiffLen); if (!res[0]) return error(res[1]); if (dcDiffLen > 0) { uint dcBuffer = sbs.readBits(dcDiffLen); dcCoefPrev[ci] += decodeCoef(dcBuffer, dcDiffLen); } block[0] = dcCoefPrev[ci]; // Read AC coefficients { uint i = 1; bool eob = false; while (!eob && i < 64) { ubyte code; res = sbs.decodeByte(tableAC.huffmanTree, &code); if (!res[0]) return error(res[1]); if (code == 0x00) // EOB, all next values are zero eob = true; else if (code == 0xF0) // ZRL, next 16 values are zero { foreach(j; 0..16) if (i < 64) { block[i] = 0; i++; } } else { ubyte hi = hiNibble(code); ubyte lo = loNibble(code); uint zeroes = hi; foreach(j; 0..zeroes) if (i < 64) { block[i] = 0; i++; } int acCoef = 0; if (lo > 0) { uint acBuffer = sbs.readBits(lo); acCoef = decodeCoef(acBuffer, lo); } if (i < 64) block[i] = acCoef; i++; } } } // Multiply block by quantization matrix foreach(i, ref v; block) v *= jpg.dqt[dqtTableId].table[i]; // Convert matrix from zig-zag order to normal order int[8*8] dctMatrix; foreach(i, v; block) dctMatrix[dezigzag[i]] = v; idct64(dctMatrix.ptr); // Copy the matrix into corresponding channel int* outMatrixPtr; if (ci == 0) outMatrixPtr = mcu.yBlocks[by * hblocks + bx].ptr; else if (ci == 1) outMatrixPtr = mcu.cbBlocks[by * hblocks + bx].ptr; else if (ci == 2) outMatrixPtr = mcu.crBlocks[by * hblocks + bx].ptr; else return error("loadJPEG error: illegal component index"); for(uint i = 0; i < 64; i++) outMatrixPtr[i] = dctMatrix[i]; } } // Convert MCU from YCbCr to RGB foreach(y; 0..mcu.height) // Pixel coordinates in MCU foreach(x; 0..mcu.width) { Color4f col = mcu.getPixel(x, y); // Pixel coordinates in image uint ix = mcuX * mcu.width + x; uint iy = mcuY * mcu.height + y; if (ix < img.width && iy < img.height) img[ix, iy] = col; } } version(JPEGDebug) { writefln("Bytes read: %s", sbs.bytesRead); } mcu.free(); return compound(img, ""); } /* * MCU struct keeps a storage for one Minimal Code Unit * and provides a generalized interface for decoding * images with different subsampling modes. * Decoder should read 8x8 blocks one by one for each channel * and fill corresponding arrays in MCU. */ struct MCU { uint width; uint height; alias Block = int[8*8]; Block[] yBlocks; Block[] cbBlocks; Block[] crBlocks; uint ySamplesH, ySamplesV; uint cbSamplesH, cbSamplesV; uint crSamplesH, crSamplesV; uint yWidth, yHeight; uint cbWidth, cbHeight; uint crWidth, crHeight; void createYBlocks(uint hsubsampling, uint vsubsampling) { yBlocks = New!(Block[])(hsubsampling * vsubsampling); width = hsubsampling * 8; height = vsubsampling * 8; ySamplesH = hsubsampling; ySamplesV = vsubsampling; yWidth = width / ySamplesH; yHeight = height / ySamplesV; } void createCbBlocks(uint hsubsampling, uint vsubsampling) { cbBlocks = New!(Block[])(hsubsampling * vsubsampling); cbSamplesH = hsubsampling; cbSamplesV = vsubsampling; cbWidth = width / cbSamplesH; cbHeight = height / cbSamplesV; } void createCrBlocks(uint hsubsampling, uint vsubsampling) { crBlocks = New!(Block[])(hsubsampling * vsubsampling); crSamplesH = hsubsampling; crSamplesV = vsubsampling; crWidth = width / crSamplesH; crHeight = height / crSamplesV; } void free() { if (yBlocks.length) Delete(yBlocks); if (cbBlocks.length) Delete(cbBlocks); if (crBlocks.length) Delete(crBlocks); } Color4f getPixel(uint x, uint y) // coordinates relative to upper-left MCU corner { // Y block coordinates uint ybx = x / yWidth; uint yby = y / yHeight; uint ybi = yby * ySamplesH + ybx; // Pixel coordinates in Y block uint ybpx = x - ybx * yWidth; uint ybpy = y - yby * yHeight; // Cb block coordinates uint cbx = x / cbWidth; uint cby = y / cbHeight; uint cbi = cby * cbSamplesH + cbx; // Pixel coordinates in Cb block uint cbpx = (x - cbx * cbWidth) / ySamplesH; uint cbpy = (y - cby * cbHeight) / ySamplesV; // Cr block coordinates uint crx = x / crWidth; uint cry = y / crHeight; uint cri = cry * crSamplesH + crx; // Pixel coordinates in Cr block uint crpx = (x - crx * crWidth) / ySamplesH; uint crpy = (y - cry * crHeight) / ySamplesV; // Get color components float Y = cast(float)yBlocks [ybi][ybpy * 8 + ybpx] + 128.0f; float Cb = cast(float)cbBlocks[cbi][cbpy * 8 + cbpx]; float Cr = cast(float)crBlocks[cri][crpy * 8 + crpx]; // Convert from YCbCr to RGB Color4f col; col.r = Y + 1.402f * Cr; col.g = Y - 0.34414f * Cb - 0.71414f * Cr; col.b = Y + 1.772f * Cb; col = col / 255.0f; col.a = 1.0f; return col; } } /* * Inverse discrete cosine transform (DCT) for 64x64 blocks * * The input coefficients should already have been multiplied by the * appropriate quantization table. We use fixed-point computation, with the * number of bits for the fractional component varying over the intermediate * stages. * * For more on the actual algorithm, see Z. Wang, "Fast algorithms for the * discrete W transform and for the discrete Fourier transform", IEEE Trans. on * ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. */ void idct64(int* src) { enum blockSize = 64; // A DCT block is 8x8. enum w1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) enum w2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) enum w3 = 2408; // 2048*sqrt(2)*cos(3*pi/16) enum w5 = 1609; // 2048*sqrt(2)*cos(5*pi/16) enum w6 = 1108; // 2048*sqrt(2)*cos(6*pi/16) enum w7 = 565; // 2048*sqrt(2)*cos(7*pi/16) enum w1pw7 = w1 + w7; enum w1mw7 = w1 - w7; enum w2pw6 = w2 + w6; enum w2mw6 = w2 - w6; enum w3pw5 = w3 + w5; enum w3mw5 = w3 - w5; enum r2 = 181; // 256/sqrt(2) // Horizontal 1-D IDCT. for (uint y = 0; y < 8; y++) { int y8 = y * 8; // If all the AC components are zero, then the IDCT is trivial. if (src[y8+1] == 0 && src[y8+2] == 0 && src[y8+3] == 0 && src[y8+4] == 0 && src[y8+5] == 0 && src[y8+6] == 0 && src[y8+7] == 0) { int dc = src[y8+0] << 3; src[y8+0] = dc; src[y8+1] = dc; src[y8+2] = dc; src[y8+3] = dc; src[y8+4] = dc; src[y8+5] = dc; src[y8+6] = dc; src[y8+7] = dc; continue; } // Prescale. int x0 = (src[y8+0] << 11) + 128; int x1 = src[y8+4] << 11; int x2 = src[y8+6]; int x3 = src[y8+2]; int x4 = src[y8+1]; int x5 = src[y8+7]; int x6 = src[y8+5]; int x7 = src[y8+3]; // Stage 1. int x8 = w7 * (x4 + x5); x4 = x8 + w1mw7*x4; x5 = x8 - w1pw7*x5; x8 = w3 * (x6 + x7); x6 = x8 - w3mw5*x6; x7 = x8 - w3pw5*x7; // Stage 2. x8 = x0 + x1; x0 -= x1; x1 = w6 * (x3 + x2); x2 = x1 - w2pw6*x2; x3 = x1 + w2mw6*x3; x1 = x4 + x6; x4 -= x6; x6 = x5 + x7; x5 -= x7; // Stage 3. x7 = x8 + x3; x8 -= x3; x3 = x0 + x2; x0 -= x2; x2 = (r2*(x4+x5) + 128) >> 8; x4 = (r2*(x4-x5) + 128) >> 8; // Stage 4. src[y8+0] = (x7 + x1) >> 8; src[y8+1] = (x3 + x2) >> 8; src[y8+2] = (x0 + x4) >> 8; src[y8+3] = (x8 + x6) >> 8; src[y8+4] = (x8 - x6) >> 8; src[y8+5] = (x0 - x4) >> 8; src[y8+6] = (x3 - x2) >> 8; src[y8+7] = (x7 - x1) >> 8; } // Vertical 1-D IDCT. for (uint x = 0; x < 8; x++) { // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial. // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so // we do not bother to check for the all-zero case. // Prescale. int y0 = (src[8*0+x] << 8) + 8192; int y1 = src[8*4+x] << 8; int y2 = src[8*6+x]; int y3 = src[8*2+x]; int y4 = src[8*1+x]; int y5 = src[8*7+x]; int y6 = src[8*5+x]; int y7 = src[8*3+x]; // Stage 1. int y8 = w7*(y4+y5) + 4; y4 = (y8 + w1mw7*y4) >> 3; y5 = (y8 - w1pw7*y5) >> 3; y8 = w3*(y6+y7) + 4; y6 = (y8 - w3mw5*y6) >> 3; y7 = (y8 - w3pw5*y7) >> 3; // Stage 2. y8 = y0 + y1; y0 -= y1; y1 = w6*(y3+y2) + 4; y2 = (y1 - w2pw6*y2) >> 3; y3 = (y1 + w2mw6*y3) >> 3; y1 = y4 + y6; y4 -= y6; y6 = y5 + y7; y5 -= y7; // Stage 3. y7 = y8 + y3; y8 -= y3; y3 = y0 + y2; y0 -= y2; y2 = (r2*(y4+y5) + 128) >> 8; y4 = (r2*(y4-y5) + 128) >> 8; // Stage 4. src[8*0+x] = (y7 + y1) >> 14; src[8*1+x] = (y3 + y2) >> 14; src[8*2+x] = (y0 + y4) >> 14; src[8*3+x] = (y8 + y6) >> 14; src[8*4+x] = (y8 - y6) >> 14; src[8*5+x] = (y0 - y4) >> 14; src[8*6+x] = (y3 - y2) >> 14; src[8*7+x] = (y7 - y1) >> 14; } } ================================================ FILE: dlib/image/io/package.d ================================================ /* Copyright (c) 2014-2025 Timur Gafarov, Martin Cejp Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Load and save images * * Copyright: Timur Gafarov, Martin Cejp 2014-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov, Martin Cejp */ module dlib.image.io; import std.path; import dlib.image.image; import dlib.image.animation; import dlib.image.hdri; public { import dlib.image.io.bmp; import dlib.image.io.hdr; import dlib.image.io.png; import dlib.image.io.tga; import dlib.image.io.jpeg; } class ImageLoadException : Exception { this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { super(msg, file, line, next); } } /** * Saves an image to file, selects encoder by filename extension */ void saveImage(SuperImage img, string filename) { switch(filename.extension) { case ".png", ".PNG": img.savePNG(filename); break; case ".bmp", ".BMP": img.saveBMP(filename); break; case ".tga", ".TGA": img.saveTGA(filename); break; default: assert(0, "Image I/O error: unsupported image format or illegal extension"); } } /** * Loads an image from file, selects decoder by filename extension */ SuperImage loadImage(string filename) { switch(filename.extension) { case ".bmp", ".BMP": return loadBMP(filename); case ".hdr", ".HDR": return loadHDR(filename); case ".jpg", ".JPG", ".jpeg", ".JPEG": return loadJPEG(filename); case ".png", ".PNG": return loadPNG(filename); case ".tga", ".TGA": return loadTGA(filename); default: assert(0, "Image I/O error: unsupported image format or illegal extension"); } } /** * Loads an animated image from file, selects decoder by filename extension */ SuperAnimatedImage loadAnimatedImage(string filename) { switch(filename.extension) { case ".png", ".apng", ".PNG", ".APNG": return loadAPNG(filename); default: assert(0, "Image I/O error: unsupported image format or illegal extension"); } } /** * Saves an animated image to file, selects encoder by filename extension */ void saveAnimatedImage(SuperAnimatedImage img, string filename) { switch(filename.extension) { case ".png", ".PNG", ".apng", ".APNG": img.saveAPNG(filename); break; default: assert(0, "Image I/O error: unsupported image format or illegal extension"); } } /** * Loads an HDR image from file, selects decoder by filename extension */ SuperImage loadHDRImage(string filename) { switch(filename.extension) { case ".hdr", ".HDR": return loadHDR(filename); default: assert(0, "Image I/O error: unsupported image format or illegal extension"); } } /** * Saves an HDR to file, selects encoder by filename extension */ void saveHDRImage(SuperHDRImage img, string filename) { switch(filename.extension) { case ".hdr", ".HDR": img.saveHDR(filename); break; default: assert(0, "Image I/O error: unsupported image format or illegal extension"); } } ================================================ FILE: dlib/image/io/png.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov, Martin Cejp, Vadim Lopatin Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Decode and encode PNG/APNG images * * Copyright: Timur Gafarov, Martin Cejp, Vadim Lopatin 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov, Martin Cejp, Vadim Lopatin */ module dlib.image.io.png; import std.stdio; import std.math; import std.string; import std.range; import dlib.core.memory; import dlib.core.stream; import dlib.core.compound; import dlib.filesystem.local; import dlib.math.utils; import dlib.math.interpolation; import dlib.coding.zlib; import dlib.image.color; import dlib.image.image; import dlib.image.animation; import dlib.image.io; // uncomment this to see debug messages: //version = PNGDebug; static const ubyte[8] PNGSignature = [137, 80, 78, 71, 13, 10, 26, 10]; // Standard chunks static const ubyte[4] IHDR = ['I', 'H', 'D', 'R']; // Image header static const ubyte[4] IEND = ['I', 'E', 'N', 'D']; // Image end static const ubyte[4] IDAT = ['I', 'D', 'A', 'T']; // Image data static const ubyte[4] PLTE = ['P', 'L', 'T', 'E']; // Palette static const ubyte[4] tRNS = ['t', 'R', 'N', 'S']; // Transparency static const ubyte[4] bKGD = ['b', 'K', 'G', 'D']; // Background color static const ubyte[4] tEXt = ['t', 'E', 'X', 't']; // Textual data (uncompressed) static const ubyte[4] zTXt = ['z', 'T', 'X', 't']; // Textual data (zlib compressed) static const ubyte[4] iTXt = ['i', 'T', 'X', 't']; // International text // Extension chunks static const ubyte[4] oFFs = ['o', 'F', 'F', 's']; // Image offset static const ubyte[4] pCAL = ['p', 'C', 'A', 'L']; // Calibration of pixel values static const ubyte[4] sCAL = ['s', 'C', 'A', 'L']; // Physical scale of image subject static const ubyte[4] gIFg = ['g', 'I', 'F', 'g']; // GIF graphic control static const ubyte[4] gIFx = ['g', 'I', 'F', 'x']; // GIF application static const ubyte[4] gIFt = ['g', 'I', 'F', 't']; // GIF plain text (deprecated) static const ubyte[4] fRAc = ['f', 'R', 'A', 'c']; // Fractal image parameters static const ubyte[4] sTER = ['s', 'T', 'E', 'R']; // Indicator of stereo image static const ubyte[4] dSIG = ['d', 'S', 'I', 'G']; // Digital signature // ImageMagick chunks static const ubyte[4] vpAg = ['v', 'p', 'A', 'g']; // VirtualPage Tags // APNG chunks static const ubyte[4] acTL = ['a', 'c', 'T', 'L']; // Animation control static const ubyte[4] fcTL = ['f', 'c', 'T', 'L']; // Frame control static const ubyte[4] fdAT = ['f', 'd', 'A', 'T']; // Frame data enum ColorType: ubyte { Greyscale = 0, // allowed bit depths: 1, 2, 4, 8 and 16 RGB = 2, // allowed bit depths: 8 and 16 Palette = 3, // allowed bit depths: 1, 2, 4 and 8 GreyscaleAlpha = 4, // allowed bit depths: 8 and 16 RGBA = 6, // allowed bit depths: 8 and 16 Any = 7 // one of the above } enum FilterMethod: ubyte { None = 0, Sub = 1, Up = 2, Average = 3, Paeth = 4 } struct PNGChunk { uint length; ubyte[4] type; ubyte[] data; uint crc; void free() { if (data.ptr) Delete(data); } } struct PNGHeader { union { struct { uint width; uint height; ubyte bitDepth; ubyte colorType; ubyte compressionMethod; ubyte filterMethod; ubyte interlaceMethod; }; ubyte[13] bytes; } } struct AnimationControlChunk { union { struct { uint numFrames; uint numPlays; }; ubyte[8] bytes; } void readFromBuffer(ubyte[] data) { *(&numFrames) = *(cast(uint*)data.ptr); numFrames = bigEndian(numFrames); *(&numPlays) = *(cast(uint*)(data.ptr+4)); numPlays = bigEndian(numPlays); } } struct OffsetChunk { int posX; int posY; ubyte unitSpecifier; void readFromBuffer(ubyte[] data) { *(&posX) = *(cast(int*)data.ptr); posX = bigEndian(posX); *(&posY) = *(cast(int*)(data.ptr+4)); posY = bigEndian(posY); *(&unitSpecifier) = *(data.ptr+8); } } enum DisposeOp: ubyte { None = 0, Background = 1, Previous = 2 } enum BlendOp: ubyte { Source = 0, Over = 1 } struct FrameControlChunk { union { struct { uint sequenceNumber; uint width; uint height; uint x; uint y; ushort delayNumerator; ushort delayDenominator; ubyte disposeOp; ubyte blendOp; }; ubyte[26] bytes; } void readFromBuffer(ubyte[] data) { *(&sequenceNumber) = *(cast(uint*)data.ptr); sequenceNumber = bigEndian(sequenceNumber); *(&width) = *(cast(uint*)(data.ptr+4)); width = bigEndian(width); *(&height) = *(cast(uint*)(data.ptr+8)); height = bigEndian(height); *(&x) = *(cast(uint*)(data.ptr+12)); x = bigEndian(x); *(&y) = *(cast(uint*)(data.ptr+16)); y = bigEndian(y); *(&delayNumerator) = *(cast(ushort*)(data.ptr+20)); delayNumerator = bigEndian(delayNumerator); *(&delayDenominator) = *(cast(ushort*)(data.ptr+22)); delayDenominator = bigEndian(delayDenominator); disposeOp = data[24]; blendOp = data[25]; } } struct PNGImage { // Common PNG data PNGHeader hdr; uint numChannels; uint bitDepth; uint bytesPerChannel; bool isAnimated = false; // Data for indexed PNG ubyte[] palette; ubyte[] transparency; uint paletteSize = 0; // APNG data uint numFrames = 1; uint numLoops = 0; bool decodingFirstFrame; FrameControlChunk frame; // Offset OffsetChunk offset; ZlibDecoder decoder; ubyte[] frameBuffer; uint frameSize; ubyte[] filteredBuffer; uint filteredBufferSize; void initDecoder() { ubyte[] buffer; if (decoder.buffer.length) { buffer = decoder.buffer; } else { uint bufferLength = hdr.width * hdr.height * numChannels * bytesPerChannel + hdr.height; buffer = New!(ubyte[])(bufferLength); } decoder = ZlibDecoder(buffer); } void initFrameBuffer() { if (frameBuffer.length) Delete(frameBuffer); if (filteredBuffer.length) Delete(filteredBuffer); frameBuffer = New!(ubyte[])(hdr.width * hdr.height * numChannels * bytesPerChannel); filteredBuffer = New!(ubyte[])(hdr.width * hdr.height * numChannels * bytesPerChannel); } void free() { if (decoder.buffer.length) Delete(decoder.buffer); if (frameBuffer.length) Delete(frameBuffer); if (filteredBuffer.length) Delete(filteredBuffer); if (palette.length) Delete(palette); if (transparency.length) Delete(transparency); } } class PNGLoadException: ImageLoadException { this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { super(msg, file, line, next); } } /** * Load PNG from file using local FileSystem. * Causes GC allocation */ SuperImage loadPNG(string filename) { InputStream input = openForInput(filename); ubyte[] data = New!(ubyte[])(cast(size_t)input.size); input.fillArray(data); ArrayStream arrStrm = New!ArrayStream(data); auto img = loadPNG(arrStrm); Delete(arrStrm); Delete(data); input.close(); return img; } /** * Load animated PNG (APNG) from file using local FileSystem. * Causes GC allocation */ SuperAnimatedImage loadAPNG(string filename) { InputStream input = openForInput(filename); ubyte[] data = New!(ubyte[])(cast(size_t)input.size); input.fillArray(data); ArrayStream arrStrm = New!ArrayStream(data); auto img = loadAPNG(arrStrm); Delete(arrStrm); Delete(data); input.close(); return img; } /** * Save PNG to file using local FileSystem. * Causes GC allocation */ void savePNG(SuperImage img, string filename) { OutputStream output = openForOutput(filename); Compound!(bool, string) res = savePNG(img, output); output.close(); if (!res[0]) throw new PNGLoadException(res[1]); } /** * Save APNG to file using local FileSystem. * Causes GC allocation */ void saveAPNG(SuperAnimatedImage img, string filename) { OutputStream output = openForOutput(filename); Compound!(bool, string) res = saveAPNG(img, output); output.close(); if (!res[0]) throw new PNGLoadException(res[1]); } /** * Load PNG from stream using default image factory. * Causes GC allocation */ SuperImage loadPNG(InputStream istrm) { Compound!(SuperImage, string) res = loadPNG(istrm, defaultImageFactory); if (res[0] is null) throw new PNGLoadException(res[1]); else return res[0]; } /// unittest { import std.base64; InputStream png() { string minimal = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADklEQVR42mL4z8AAEGAAAwEBAGb9nyQAAAAASUVORK5CYII="; ubyte[] bytes = Base64.decode(minimal); return new ArrayStream(bytes, bytes.length); } SuperImage img = loadPNG(png()); assert(img.width == 1); assert(img.height == 1); assert(img.channels == 3); assert(img.pixelSize == 3); assert(img.data == [0xff, 0x00, 0x00]); createDir("tests", false); savePNG(img, "tests/minimal.png"); loadPNG("tests/minimal.png"); } /** * Load animated PNG (APNG) from stream using default animated image factory. * Causes GC allocation */ SuperAnimatedImage loadAPNG(InputStream istrm) { Compound!(SuperImage, string) res = loadPNG(istrm, animatedImageFactory); if (res[0] is null) throw new PNGLoadException(res[1]); else return cast(SuperAnimatedImage)res[0]; } /** * Load PNG from stream using specified image factory. * GC-free */ Compound!(SuperImage, string) loadPNG( InputStream istrm, SuperImageFactory imgFac) { PNGImage png; SuperImage img = null; SuperAnimatedImage animImg = null; SuperImage tmpImg = null; void finalize() { png.free(); if (tmpImg) { tmpImg.free(); tmpImg = null; } // don't close the stream, just release our reference istrm = null; } Compound!(SuperImage, string) error(string errorMsg) { finalize(); if (img) { img.free(); img = null; } return compound(img, errorMsg); } ubyte[8] signatureBuffer; if (!istrm.fillArray(signatureBuffer)) { return error("loadPNG error: signature check failed"); } version(PNGDebug) { writeln("----------------"); writeln("PNG Signature: ", signatureBuffer); writeln("----------------"); } bool endChunk = false; while (!endChunk && istrm.readable) { PNGChunk chunk; auto res = readChunk(&png, istrm, &chunk); if (!res[0]) { chunk.free(); return error(res[1]); } else { if (chunk.type == IEND) { endChunk = true; chunk.free(); } else if (chunk.type == IHDR) { res = readIHDR(&png, &chunk); chunk.free(); if (!res[0]) { return error(res[1]); } png.decodingFirstFrame = true; png.frame.width = png.hdr.width; png.frame.height = png.hdr.height; png.frame.x = 0; png.frame.y = 0; png.initFrameBuffer(); png.initDecoder(); } else if (chunk.type == IDAT) { png.decoder.decode(chunk.data); chunk.free(); } else if (chunk.type == PLTE) { png.palette = chunk.data; } else if (chunk.type == tRNS) { png.transparency = chunk.data; if (png.hdr.colorType == ColorType.Palette) { if (png.transparency.length > 0) png.numChannels = 4; else png.numChannels = 3; } version(PNGDebug) { writefln("transparency.length = %s", png.transparency.length); } png.initFrameBuffer(); png.initDecoder(); } else if (chunk.type == oFFs) { png.offset.readFromBuffer(chunk.data); chunk.free(); version(PNGDebug) { writefln("posX = %s", png.offset.posX); writefln("posY = %s", png.offset.posY); writefln("unitSpecifier = %s", png.offset.unitSpecifier); } } else if (chunk.type == acTL) { AnimationControlChunk animControl; animControl.readFromBuffer(chunk.data); png.numFrames = animControl.numFrames; png.numLoops = animControl.numPlays; png.isAnimated = true; version(PNGDebug) { writefln("numFrames = %s", png.numFrames); writefln("numLoops = %s", png.numLoops); } chunk.free(); } else if (chunk.type == fcTL) { png.frame.readFromBuffer(chunk.data); version(PNGDebug) { writefln("sequenceNumber = %s", png.frame.sequenceNumber); writefln("frameWidth = %s", png.frame.width); writefln("frameHeight = %s", png.frame.height); writefln("frameX = %s", png.frame.x); writefln("frameY = %s", png.frame.y); writefln("delayNumerator = %s", png.frame.delayNumerator); writefln("delayDenominator = %s", png.frame.delayDenominator); writefln("disposeOp = %s", cast(DisposeOp)png.frame.disposeOp); writefln("blendOp = %s", cast(BlendOp)png.frame.blendOp); } png.initDecoder(); chunk.free(); } else if (chunk.type == fdAT) { uint dataSequenceNumber; *(&dataSequenceNumber) = *(cast(uint*)chunk.data.ptr); dataSequenceNumber = bigEndian(dataSequenceNumber); version(PNGDebug) { writefln("sequenceNumber = %s", dataSequenceNumber); } png.decoder.decode(chunk.data[4..$]); chunk.free(); } else { chunk.free(); } version(PNGDebug) { writeln("----------------"); } } if (png.decoder.hasEnded) { if (img is null) { tmpImg = imgFac.createImage(png.hdr.width, png.hdr.height, png.numChannels, png.bitDepth); img = imgFac.createImage(png.hdr.width, png.hdr.height, png.numChannels, png.bitDepth, png.numFrames); if (png.isAnimated) animImg = cast(SuperAnimatedImage)img; } res = fillFrame(&png); if (res[0]) { if (png.decodingFirstFrame) { png.decodingFirstFrame = false; if (tmpImg.data.length != png.frameBuffer.length) { return error("loadPNG error: uncompressed data length mismatch"); } tmpImg.data[] = png.frameBuffer[0..png.frameSize]; img.data[] = tmpImg.data[]; if (animImg) { disposeFrame(&png, animImg, tmpImg, true); } } else { blitFrame(&png, png.frameBuffer, tmpImg); img.data[] = tmpImg.data[]; uint f = animImg.currentFrame; animImg.currentFrame = f - 1; disposeFrame(&png, animImg, tmpImg, false); animImg.currentFrame = f; } if (animImg) { if (animImg.currentFrame == animImg.numFrames-1) { // Last frame, stop here animImg.currentFrame = 0; break; } } } else { return error(res[1]); } if (animImg) { animImg.advanceFrame(); } else { // Stop decoding if we don't need animation break; } } } finalize(); return compound(img, ""); } /** * Load animated PNG (APNG) from stream using specified image factory. * GC-free */ Compound!(SuperAnimatedImage, string) loadAPNG( InputStream istrm, SuperImageFactory imgFac) { SuperAnimatedImage img = null; auto res = loadPNG(istrm, imgFac); if (res[0]) img = cast(SuperAnimatedImage)res[0]; return compound(img, res[1]); } /** * Save APNG to stream. * GC-free */ Compound!(bool, string) saveAPNG(SuperAnimatedImage img, OutputStream output) in { assert (img.data.length); } do { ubyte[] raw; ubyte[] buffer; void finalize() { if (buffer.length) Delete(buffer); if (raw.length) Delete(raw); } Compound!(bool, string) error(string errorMsg) { finalize(); return compound(false, errorMsg); } if (img.bitDepth != 8) return error("savePNG error: only 8-bit images are supported by encoder"); bool writeChunk(ubyte[4] chunkType, ubyte[] chunkData) { PNGChunk hdrChunk; hdrChunk.length = cast(uint)chunkData.length; hdrChunk.type = chunkType; hdrChunk.data = chunkData; hdrChunk.crc = crc32(chain(chunkType[0..$], hdrChunk.data)); if (!output.writeBE!uint(hdrChunk.length) || !output.writeArray(hdrChunk.type)) return false; if (chunkData.length) if (!output.writeArray(hdrChunk.data)) return false; if (!output.writeBE!uint(hdrChunk.crc)) return false; return true; } bool writeHeader() { PNGHeader hdr; hdr.width = networkByteOrder(img.width); hdr.height = networkByteOrder(img.height); hdr.bitDepth = 8; if (img.channels == 4) hdr.colorType = ColorType.RGBA; else if (img.channels == 3) hdr.colorType = ColorType.RGB; else if (img.channels == 2) hdr.colorType = ColorType.GreyscaleAlpha; else if (img.channels == 1) hdr.colorType = ColorType.Greyscale; hdr.compressionMethod = 0; hdr.filterMethod = 0; hdr.interlaceMethod = 0; return writeChunk(IHDR, hdr.bytes); } uint seqNumber = 0; bool writeAnimationControlChunk() { AnimationControlChunk actl; actl.numFrames = networkByteOrder(img.numFrames); actl.numPlays = networkByteOrder(0); return writeChunk(acTL, actl.bytes); } bool writeFrameControlChunk() { FrameControlChunk fctl; fctl.sequenceNumber = networkByteOrder(seqNumber); seqNumber++; fctl.width = networkByteOrder(img.width); fctl.height = networkByteOrder(img.height); fctl.x = networkByteOrder(0); fctl.y = networkByteOrder(0); // TODO: add timeStep to SuperAnimatedImage fctl.delayNumerator = networkByteOrder(75); fctl.delayDenominator = networkByteOrder(1000); fctl.disposeOp = DisposeOp.Background; fctl.blendOp = BlendOp.Source; return writeChunk(fcTL, fctl.bytes); } bool writeFrameDataChunk(ubyte[] data) { uint len = cast(uint)data.length + 4; ubyte[4] type = fdAT; uint seq = seqNumber; uint seqBE = networkByteOrder(seqNumber); seqNumber++; ubyte[4] seqNumberBytes; seqNumberBytes = (cast(ubyte*)&seqBE)[0..4][]; uint crc = crc32(chain(type[0..$], seqNumberBytes[0..$], data)); if (!output.writeBE!uint(len) || !output.writeArray(type)) return false; if (!output.writeBE!uint(seq)) return false; if (data.length) if (!output.writeArray(data)) return false; if (!output.writeBE!uint(crc)) return false; return true; } output.writeArray(PNGSignature); if (!writeHeader()) return error("savePNG error: write failed (disk full?)"); if (!writeAnimationControlChunk()) return error("savePNG error: write failed (disk full?)"); //TODO: filtering raw = New!(ubyte[])(img.width * img.height * img.channels + img.height); buffer = New!(ubyte[])(64 * 1024); bool encode(uint frame) { if (!writeFrameControlChunk()) return false; foreach(y; 0..img.height) { auto rowStart = y * (img.width * img.channels + 1); raw[rowStart] = 0; // No filter foreach(x; 0..img.width) { auto dataIndex = (y * img.width + x) * img.channels; auto rawIndex = rowStart + 1 + x * img.channels; foreach(ch; 0..img.channels) raw[rawIndex + ch] = img.data[dataIndex + ch]; } } ZlibBufferedEncoder zlibEncoder = ZlibBufferedEncoder(buffer, raw); while (!zlibEncoder.ended) { auto len = zlibEncoder.encode(); if (len > 0) { bool res; if (frame == 0) res = writeChunk(IDAT, zlibEncoder.buffer[0..len]); else res = writeFrameDataChunk(zlibEncoder.buffer[0..len]); if (!res) return false; } } return true; } uint startFrame = img.currentFrame; foreach(f; 0..img.numFrames) { img.currentFrame = f; if (!encode(f)) return error("savePNG error: write failed (disk full?)"); } img.currentFrame = startFrame; writeChunk(IEND, []); finalize(); return compound(true, ""); } /** * Save PNG to stream. * GC-free */ Compound!(bool, string) savePNG(SuperImage img, OutputStream output) in { assert (img.data.length); } do { Compound!(bool, string) error(string errorMsg) { return compound(false, errorMsg); } if (img.bitDepth != 8) return error("savePNG error: only 8-bit images are supported by encoder"); bool writeChunk(ubyte[4] chunkType, ubyte[] chunkData) { PNGChunk hdrChunk; hdrChunk.length = cast(uint)chunkData.length; hdrChunk.type = chunkType; hdrChunk.data = chunkData; hdrChunk.crc = crc32(chain(chunkType[0..$], hdrChunk.data)); if (!output.writeBE!uint(hdrChunk.length) || !output.writeArray(hdrChunk.type)) return false; if (chunkData.length) if (!output.writeArray(hdrChunk.data)) return false; if (!output.writeBE!uint(hdrChunk.crc)) return false; return true; } bool writeHeader() { PNGHeader hdr; hdr.width = networkByteOrder(img.width); hdr.height = networkByteOrder(img.height); hdr.bitDepth = 8; if (img.channels == 4) hdr.colorType = ColorType.RGBA; else if (img.channels == 3) hdr.colorType = ColorType.RGB; else if (img.channels == 2) hdr.colorType = ColorType.GreyscaleAlpha; else if (img.channels == 1) hdr.colorType = ColorType.Greyscale; hdr.compressionMethod = 0; hdr.filterMethod = 0; hdr.interlaceMethod = 0; return writeChunk(IHDR, hdr.bytes); } output.writeArray(PNGSignature); if (!writeHeader()) return error("savePNG error: write failed (disk full?)"); //TODO: filtering ubyte[] raw = New!(ubyte[])(img.width * img.height * img.channels + img.height); foreach(y; 0..img.height) { auto rowStart = y * (img.width * img.channels + 1); raw[rowStart] = 0; // No filter foreach(x; 0..img.width) { auto dataIndex = (y * img.width + x) * img.channels; auto rawIndex = rowStart + 1 + x * img.channels; foreach(ch; 0..img.channels) raw[rawIndex + ch] = img.data[dataIndex + ch]; } } ubyte[] buffer = New!(ubyte[])(64 * 1024); ZlibBufferedEncoder zlibEncoder = ZlibBufferedEncoder(buffer, raw); while (!zlibEncoder.ended) { auto len = zlibEncoder.encode(); if (len > 0) writeChunk(IDAT, zlibEncoder.buffer[0..len]); } writeChunk(IEND, []); Delete(buffer); Delete(raw); return compound(true, ""); } Compound!(bool, string) err(string msg) { return compound(false, msg); } Compound!(bool, string) suc() { return compound(true, ""); } Compound!(bool, string) readChunk( PNGImage* png, InputStream istrm, PNGChunk* chunk) { if (!istrm.readBE!uint(&chunk.length) || !istrm.fillArray(chunk.type)) { return err("loadPNG error: failed to read chunk, invalid PNG stream"); } version(PNGDebug) writefln("Chunk length = %s", chunk.length); version(PNGDebug) writefln("Chunk type = %s", cast(char[])chunk.type); if (chunk.length > 0) { chunk.data = New!(ubyte[])(chunk.length); if (!istrm.fillArray(chunk.data)) { return err("loadPNG error: failed to read chunk data, invalid PNG stream"); } } version(PNGDebug) writefln("Chunk data.length = %s", chunk.data.length); if (!istrm.readBE!uint(&chunk.crc)) { return err("loadPNG error: failed to read chunk CRC, invalid PNG stream"); } /* uint calculatedCRC = crc32(chain(chunk.type[0..$], chunk.data)); version(PNGDebug) { writefln("Chunk CRC = %X", chunk.crc); writefln("Calculated CRC = %X", calculatedCRC); } if (chunk.crc != calculatedCRC) { return err("loadPNG error: chunk CRC check failed"); } */ return suc(); } Compound!(bool, string) readIHDR( PNGImage* png, PNGChunk* chunk) { PNGHeader* hdr = &png.hdr; if (chunk.data.length < hdr.bytes.length) return err("loadPNG error: illegal header chunk"); hdr.bytes[] = chunk.data[0..hdr.bytes.length]; hdr.width = bigEndian(hdr.width); hdr.height = bigEndian(hdr.height); version(PNGDebug) { writefln("width = %s", hdr.width); writefln("height = %s", hdr.height); writefln("bitDepth = %s", hdr.bitDepth); writefln("colorType = %s", hdr.colorType); writefln("compressionMethod = %s", hdr.compressionMethod); writefln("filterMethod = %s", hdr.filterMethod); writefln("interlaceMethod = %s", hdr.interlaceMethod); } bool supportedIndexed = (hdr.colorType == ColorType.Palette) && (hdr.bitDepth == 1 || hdr.bitDepth == 2 || hdr.bitDepth == 4 || hdr.bitDepth == 8); if (hdr.bitDepth != 8 && hdr.bitDepth != 16 && !supportedIndexed) return err("loadPNG error: unsupported bit depth"); if (hdr.compressionMethod != 0) return err("loadPNG error: unsupported compression method"); if (hdr.filterMethod != 0) return err("loadPNG error: unsupported filter method"); if (hdr.interlaceMethod != 0) return err("loadPNG error: interlacing is not supported"); if (hdr.colorType == ColorType.Greyscale) png.numChannels = 1; else if (hdr.colorType == ColorType.GreyscaleAlpha) png.numChannels = 2; else if (hdr.colorType == ColorType.RGB) png.numChannels = 3; else if (hdr.colorType == ColorType.RGBA) png.numChannels = 4; else if (hdr.colorType == ColorType.Palette) { if (png.transparency.length > 0) png.numChannels = 4; else png.numChannels = 3; } else return err("loadPNG error: unsupported color type"); if (hdr.colorType == ColorType.Palette) png.bitDepth = 8; else png.bitDepth = hdr.bitDepth; png.bytesPerChannel = png.bitDepth / 8; version(PNGDebug) { writefln("bytesPerChannel = %s", png.bytesPerChannel); } return suc(); } Compound!(bool, string) fillFrame(PNGImage* png) { ubyte[] decodedBuffer = png.decoder.buffer; version(PNGDebug) writefln("decodedBuffer.length = %s", decodedBuffer.length); bool indexed = (png.hdr.colorType == ColorType.Palette); uint calculatedSize; if (indexed) calculatedSize = (png.frame.width * png.frame.height * png.hdr.bitDepth) / 8 + png.frame.height; else calculatedSize = png.frame.width * png.frame.height * (png.hdr.bitDepth / 8) * png.numChannels + png.frame.height; png.frameSize = png.frame.width * png.frame.height * png.numChannels * png.bytesPerChannel; png.filteredBufferSize = calculatedSize - png.frame.height; version(PNGDebug) { writefln("calculatedSize = %s", calculatedSize); writefln("frameSize = %s", png.frameSize); writefln("filteredBufferSize = %s", png.filteredBufferSize); writefln("png.frameBuffer.length = %s", png.frameBuffer.length); } ubyte[] pdata = png.frameBuffer[0..png.frameSize]; ubyte[] filteredBuffer = png.filteredBuffer[0..png.filteredBufferSize]; if (decodedBuffer.length != calculatedSize) { return err("loadPNG error: image size and data mismatch"); } // apply filtering to the image data auto res = filter(png, indexed, decodedBuffer, filteredBuffer); if (!res[0]) { return err(res[1]); } // if a palette is used, substitute target colors if (indexed) { if (png.palette.length == 0) return err("loadPNG error: palette chunk not found"); if (png.hdr.bitDepth == 8) { for (int i = 0; i < filteredBuffer.length; ++i) { ubyte b = filteredBuffer[i]; pdata[i * png.numChannels + 0] = png.palette[b * 3 + 0]; pdata[i * png.numChannels + 1] = png.palette[b * 3 + 1]; pdata[i * png.numChannels + 2] = png.palette[b * 3 + 2]; if (png.transparency.length > 0) pdata[i * png.numChannels + 3] = b < png.transparency.length ? png.transparency[b] : 255; } } else // bit depths 1, 2, 4 { int srcindex = 0; int srcshift = 8 - png.hdr.bitDepth; ubyte mask = cast(ubyte)((1 << png.hdr.bitDepth) - 1); int sz = png.frame.width * png.frame.height; for (int dstindex = 0; dstindex < sz; dstindex++) { auto b = ((filteredBuffer[srcindex] >> srcshift) & mask); pdata[dstindex * png.numChannels + 0] = png.palette[b * 3 + 0]; pdata[dstindex * png.numChannels + 1] = png.palette[b * 3 + 1]; pdata[dstindex * png.numChannels + 2] = png.palette[b * 3 + 2]; if (png.transparency.length > 0) pdata[dstindex * png.numChannels + 3] = b < png.transparency.length ? png.transparency[b] : 255; if (srcshift <= 0) { srcshift = 8 - png.hdr.bitDepth; srcindex++; } else { srcshift -= png.hdr.bitDepth; } } } } else { pdata[] = filteredBuffer[]; } return suc(); } void blitFrame( PNGImage* png, ubyte[] frameBuffer, SuperImage img) { for(uint y = 0; y < png.frame.height; y++) { for(uint x = 0; x < png.frame.width; x++) { Color4f c1 = img[png.frame.x + x, png.frame.y + y]; Color4f c2 = getColor(png, frameBuffer, x, y); if (png.frame.blendOp == BlendOp.Source) img[png.frame.x + x, png.frame.y + y] = c2; else img[png.frame.x + x, png.frame.y + y] = alphaOver(c1, c2); } } } void disposeFrame( PNGImage* png, SuperImage prevImg, SuperImage img, bool firstFrame) { if (png.frame.disposeOp != DisposeOp.None) for(uint y = 0; y < png.hdr.height; y++) { for(uint x = 0; x < png.hdr.width; x++) { if (png.frame.disposeOp == DisposeOp.Previous && !firstFrame) img[x, y] = prevImg[x, y]; else img[x, y] = Color4f(0, 0, 0, 0); } } } Color4f getColor( PNGImage* png, ubyte[] pixData, uint x, uint y) { uint bitDepth = png.bitDepth; uint channels = png.numChannels; uint pixelSize = png.bytesPerChannel * channels; uint index = (y * png.frame.width + x) * pixelSize; uint maxv = (2 ^^ bitDepth) - 1; Color4 res = Color4(0, 0, 0, 0); if (channels == 1 && bitDepth == 8) { auto v = pixData[index]; res = Color4(v, v, v); } else if (channels == 2 && bitDepth == 8) { auto v = pixData[index]; res = Color4(v, v, v, pixData[index+1]); } else if (channels == 3 && bitDepth == 8) { res = Color4(pixData[index], pixData[index+1], pixData[index+2], cast(ubyte)maxv); } else if (channels == 4 && bitDepth == 8) { res = Color4(pixData[index], pixData[index+1], pixData[index+2], pixData[index+3]); } else if (channels == 1 && bitDepth == 16) { ushort v = pixData[index] << 8 | pixData[index+1]; res = Color4(v, v, v); } else if (channels == 2 && bitDepth == 16) { ushort v = pixData[index] << 8 | pixData[index+1]; ushort a = pixData[index+2] << 8 | pixData[index+3]; res = Color4(v, v, v, a); } else if (channels == 3 && bitDepth == 16) { ushort r = pixData[index] << 8 | pixData[index+1]; ushort g = pixData[index+2] << 8 | pixData[index+3]; ushort b = pixData[index+4] << 8 | pixData[index+5]; ushort a = cast(ushort)maxv; res = Color4(r, g, b, a); } else if (channels == 4 && bitDepth == 16) { ushort r = pixData[index] << 8 | pixData[index+1]; ushort g = pixData[index+2] << 8 | pixData[index+3]; ushort b = pixData[index+4] << 8 | pixData[index+5]; ushort a = pixData[index+6] << 8 | pixData[index+7]; res = Color4(r, g, b, a); } else assert(0); return Color4f(res, bitDepth); } /* * Performs the paeth PNG filter from pixels values: * a = back * b = up * c = up and back */ pure ubyte paeth(ubyte a, ubyte b, ubyte c) { int p = a + b - c; int pa = std.math.abs(p - a); int pb = std.math.abs(p - b); int pc = std.math.abs(p - c); if (pa <= pb && pa <= pc) return a; else if (pb <= pc) return b; else return c; } Compound!(bool, string) filter( PNGImage* png, bool indexed, ubyte[] ibuffer, ubyte[] obuffer) { uint width = png.frame.width; uint height = png.frame.height; uint channels = png.numChannels; uint bytesPerPixel = png.hdr.bitDepth / 8; // 1 for 8bit, 2 for 16bit uint scanlineSize; if (indexed) scanlineSize = (width * png.hdr.bitDepth) / 8 + 1; else scanlineSize = width * bytesPerPixel * channels + 1; ubyte pback, pup, pupback, cbyte; for (int i = 0; i < height; ++i) { pback = 0; // get the first byte of a scanline ubyte scanFilter = ibuffer[i * scanlineSize]; if (indexed) { width = scanlineSize - 1; for (int j = 0; j < width; ++j) { if (i == 0) pup = 0; else pup = obuffer[(i-1) * width + j]; if (j == 0) pback = 0; else pback = obuffer[i * width + j-1]; if (i == 0 || j == 0) pupback = 0; else pupback = obuffer[(i-1) * width + j-1]; cbyte = ibuffer[i * scanlineSize + j+1]; // filter, then set the current byte in data switch (scanFilter) { case FilterMethod.None: obuffer[i * width + j] = cbyte; break; case FilterMethod.Sub: obuffer[i * width + j] = cast(ubyte)(cbyte + pback); break; case FilterMethod.Up: obuffer[i * width + j] = cast(ubyte)(cbyte + pup); break; case FilterMethod.Average: obuffer[i * width + j] = cast(ubyte)(cbyte + (pback + pup) / 2); break; case FilterMethod.Paeth: obuffer[i * width + j] = cast(ubyte)(cbyte + paeth(pback, pup, pupback)); break; default: return err(format("loadPNG error: unknown scanline filter (%s)", scanFilter)); } } } else { for (int j = 0; j < width; ++j) { for (int k = 0; k < bytesPerPixel * channels; ++k) { if (i == 0) pup = 0; else pup = obuffer[((i-1) * width + j) * bytesPerPixel * channels + k]; if (j == 0) pback = 0; else pback = obuffer[(i * width + j-1) * bytesPerPixel * channels + k]; if (i == 0 || j == 0) pupback = 0; else pupback = obuffer[((i-1) * width + j-1) * bytesPerPixel * channels + k]; // get the current byte from ibuffer cbyte = ibuffer[i * (width * bytesPerPixel * channels + 1) + j * bytesPerPixel * channels + k + 1]; // filter, then set the current byte in data switch (scanFilter) { case FilterMethod.None: obuffer[(i * width + j) * bytesPerPixel * channels + k] = cbyte; break; case FilterMethod.Sub: obuffer[(i * width + j) * bytesPerPixel * channels + k] = cast(ubyte)(cbyte + pback); break; case FilterMethod.Up: obuffer[(i * width + j) * bytesPerPixel * channels + k] = cast(ubyte)(cbyte + pup); break; case FilterMethod.Average: obuffer[(i * width + j) * bytesPerPixel * channels + k] = cast(ubyte)(cbyte + (pback + pup) / 2); break; case FilterMethod.Paeth: obuffer[(i * width + j) * bytesPerPixel * channels + k] = cast(ubyte)(cbyte + paeth(pback, pup, pupback)); break; default: return err(format("loadPNG error: unknown scanline filter (%s)", scanFilter)); } } } } } return suc(); } uint crc32(R)(R range, uint inCrc = 0) if (isInputRange!R) { uint[256] generateTable() { uint[256] table; uint crc; for (int i = 0; i < 256; i++) { crc = i; for (int j = 0; j < 8; j++) crc = crc & 1 ? (crc >> 1) ^ 0xEDB88320UL : crc >> 1; table[i] = crc; } return table; } static const uint[256] table = generateTable(); uint crc; crc = inCrc ^ 0xFFFFFFFF; foreach(v; range) crc = (crc >> 8) ^ table[(crc ^ v) & 0xFF]; return (crc ^ 0xFFFFFFFF); } ================================================ FILE: dlib/image/io/tga.d ================================================ /* Copyright (c) 2014-2025 Timur Gafarov, Roman Chistokhodov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Decode JPEG images * * Copyright: Timur Gafarov, Roman Chistokhodov 2014-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov, Roman Chistokhodov */ module dlib.image.io.tga; import std.stdio; import std.file; import std.conv; import dlib.core.memory; import dlib.core.stream; import dlib.core.compound; import dlib.image.color; import dlib.image.image; import dlib.image.io; import dlib.image.io.utils; import dlib.filesystem.local; // uncomment this to see debug messages: //version = TGADebug; struct TGAHeader { ubyte idLength; ubyte type; ubyte encoding; short colmapStart; short colmapLen; ubyte colmapBits; short xstart; short ystart; short width; short height; ubyte bpp; ubyte descriptor; } enum TGAEncoding : ubyte { Indexed = 1, RGB = 2, Grey = 3, RLE_Indexed = 9, RLE_RGB = 10, RLE_Grey = 11 }; enum TgaOrigin : ubyte { Left = 0x00, Right = 0x10, Lower = 0x00, Upper = 0x20 } class TGALoadException: ImageLoadException { this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { super(msg, file, line, next); } } /** * Load PNG from file using local FileSystem. * Causes GC allocation */ SuperImage loadTGA(string filename) { InputStream input = openForInput(filename); try { ubyte[] data = New!(ubyte[])(cast(size_t)input.size); input.fillArray(data); ArrayStream arrStrm = New!ArrayStream(data); auto img = loadTGA(arrStrm); Delete(arrStrm); Delete(data); return img; } catch (TGALoadException ex) { throw new Exception("'" ~ filename ~ "' :" ~ ex.msg, ex.file, ex.line, ex.next); } finally { input.close(); } } /** * Load TGA from stream using default image factory. * Causes GC allocation */ SuperImage loadTGA(InputStream istrm) { Compound!(SuperImage, string) res = loadTGA(istrm, defaultImageFactory); if (res[0] is null) throw new TGALoadException(res[1]); else return res[0]; } /** * Load TGA from stream using specified image factory. * GC-free */ Compound!(SuperImage, string) loadTGA( InputStream istrm, SuperImageFactory imgFac) { SuperImage img = null; Compound!(SuperImage, string) error(string errorMsg) { if (img) { img.free(); img = null; } return compound(img, errorMsg); } TGAHeader readHeader() { TGAHeader hdr = readStruct!TGAHeader(istrm); version(TGADebug) { writefln("idLength = %s", hdr.idLength); writefln("type = %s", hdr.type); /* * Encoding flag: * 1 = Raw indexed image * 2 = Raw RGB * 3 = Raw greyscale * 9 = RLE indexed * 10 = RLE RGB * 11 = RLE greyscale * 32 & 33 = Other compression, indexed */ writefln("encoding = %s", hdr.encoding); writefln("colmapStart = %s", hdr.colmapStart); writefln("colmapLen = %s", hdr.colmapLen); writefln("colmapBits = %s", hdr.colmapBits); writefln("xstart = %s", hdr.xstart); writefln("ystart = %s", hdr.ystart); writefln("width = %s", hdr.width); writefln("height = %s", hdr.height); writefln("bpp = %s", hdr.bpp); writefln("descriptor = %s", hdr.descriptor); writeln("-------------------"); } return hdr; } SuperImage readRawRGB(ref TGAHeader hdr) { uint channels = hdr.bpp / 8; SuperImage res = imgFac.createImage(hdr.width, hdr.height, channels, 8); if (hdr.descriptor & TgaOrigin.Upper) { istrm.fillArray(res.data); } else { foreach(i; 0..hdr.height) { istrm.fillArray(res.data[channels * hdr.width * (hdr.height-i-1)..channels * hdr.width * (hdr.height-i)]); } } const ubyte alphaBits = cast(ubyte)(hdr.descriptor & 0xf); version(TGADebug) writefln("Alpha bits: %s", alphaBits); if (channels == 4) { for (size_t i=0; i 0) - (dx < 0); int dx2 = abs(dx) * 2; int dy = y2 - y1; int iy = (dy > 0) - (dy < 0); int dy2 = abs(dy) * 2; img[x1, y1] = color; if (dx2 >= dy2) { int error = dy2 - (dx2 / 2); while (x1 != x2) { if (error >= 0 && (error || (ix > 0))) { error -= dx2; y1 += iy; } error += dy2; x1 += ix; img[x1, y1] = color; } } else { int error = dx2 - (dy2 / 2); while (y1 != y2) { if (error >= 0 && (error || (iy > 0))) { error -= dy2; x1 += ix; } error += dx2; y1 += iy; img[x1, y1] = color; } } } /// Draw a filled circle void drawCircle(SuperImage img, Color4f col, int x0, int y0, uint r) { int f = 1 - r; int ddF_x = 0; int ddF_y = -2 * r; int x = 0; int y = r; img[x0, y0 + r] = col; img[x0, y0 - r] = col; img[x0 + r, y0] = col; img[x0 - r, y0] = col; while(x < y) { if(f >= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x + 1; img[x0 + x, y0 + y] = col; img[x0 - x, y0 + y] = col; img[x0 + x, y0 - y] = col; img[x0 - x, y0 - y] = col; img[x0 + y, y0 + x] = col; img[x0 - y, y0 + x] = col; img[x0 + y, y0 - x] = col; img[x0 - y, y0 - x] = col; } } /// Draw a filled rectangle void drawRect(SuperImage img, Color4f col, int x1, int y1, int x2, int y2) { int minX = x1 < x2 ? x1 : x2; int maxX = x1 > x2 ? x1 : x2; int minY = y1 < y2 ? y1 : y2; int maxY = y1 > y2 ? y1 : y2; foreach (x; minX..maxX + 1) foreach (y; minY..maxY + 1) img[x, y] = col; } ================================================ FILE: dlib/image/render/text.d ================================================ /* Copyright (c) 2022-2025 Oleg Baharev, Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Draw ASCII text * * Copyright: Oleg Baharev, Timur Gafarov 2022-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Oleg Baharev, Timur Gafarov */ module dlib.image.render.text; import dlib.image.color; import dlib.image.image; /// Draw text on an image void drawText(SuperImage img, string s, int x, int y, Color4f color) { foreach(i, c; s) { foreach(cx; 0..5) foreach(cy; 0..8) { Color4f col = sampleChar(c, cx, cy, color); uint px = x + 6 * cast(int)i + cx; uint py = y + cy; img[px, py] = alphaOver(img[px, py], col); } } } Color4f sampleChar(char c, int x, int y, Color4f color) { uint index = cast(uint)c * 5; if (x < 0) x = 0; if (x >= 5) x = 4; if (y < 0) y = 0; if (y >= 5 * 8) y = 5 * 8 - 1; ubyte col = font[index + x]; auto val = col & (0x01 << y); if (val) return color; else return Color4f(0, 0, 0, 0); } /* * 5x8 ASCII character data. * Each 5 byte chunk represents one character (5 columns, 8 bits per column) */ immutable(ubyte)[] font = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x5B, 0x4F, 0x5B, 0x3E, 0x3E, 0x6B, 0x4F, 0x6B, 0x3E, 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, 0x18, 0x3C, 0x7E, 0x3C, 0x18, 0x1C, 0x57, 0x7D, 0x57, 0x1C, 0x1C, 0x5E, 0x7F, 0x5E, 0x1C, 0x00, 0x18, 0x3C, 0x18, 0x00, 0xFF, 0xE7, 0xC3, 0xE7, 0xFF, 0x00, 0x18, 0x24, 0x18, 0x00, 0xFF, 0xE7, 0xDB, 0xE7, 0xFF, 0x30, 0x48, 0x3A, 0x06, 0x0E, 0x26, 0x29, 0x79, 0x29, 0x26, 0x40, 0x7F, 0x05, 0x05, 0x07, 0x40, 0x7F, 0x05, 0x25, 0x3F, 0x5A, 0x3C, 0xE7, 0x3C, 0x5A, 0x7F, 0x3E, 0x1C, 0x1C, 0x08, 0x08, 0x1C, 0x1C, 0x3E, 0x7F, 0x14, 0x22, 0x7F, 0x22, 0x14, 0x5F, 0x5F, 0x00, 0x5F, 0x5F, 0x06, 0x09, 0x7F, 0x01, 0x7F, 0x00, 0x66, 0x89, 0x95, 0x6A, 0x60, 0x60, 0x60, 0x60, 0x60, 0x94, 0xA2, 0xFF, 0xA2, 0x94, 0x08, 0x04, 0x7E, 0x04, 0x08, 0x10, 0x20, 0x7E, 0x20, 0x10, 0x08, 0x08, 0x2A, 0x1C, 0x08, 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x1E, 0x10, 0x10, 0x10, 0x10, 0x0C, 0x1E, 0x0C, 0x1E, 0x0C, 0x30, 0x38, 0x3E, 0x38, 0x30, 0x06, 0x0E, 0x3E, 0x0E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x23, 0x13, 0x08, 0x64, 0x62, 0x36, 0x49, 0x56, 0x20, 0x50, 0x00, 0x08, 0x07, 0x03, 0x00, 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, 0x41, 0x22, 0x1C, 0x00, 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x80, 0x70, 0x30, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x60, 0x60, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x42, 0x7F, 0x40, 0x00, 0x72, 0x49, 0x49, 0x49, 0x46, 0x21, 0x41, 0x49, 0x4D, 0x33, 0x18, 0x14, 0x12, 0x7F, 0x10, 0x27, 0x45, 0x45, 0x45, 0x39, 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x41, 0x21, 0x11, 0x09, 0x07, 0x36, 0x49, 0x49, 0x49, 0x36, 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x34, 0x00, 0x00, 0x00, 0x08, 0x14, 0x22, 0x41, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x41, 0x22, 0x14, 0x08, 0x02, 0x01, 0x59, 0x09, 0x06, 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x7F, 0x49, 0x49, 0x49, 0x36, 0x3E, 0x41, 0x41, 0x41, 0x22, 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x7F, 0x49, 0x49, 0x49, 0x41, 0x7F, 0x09, 0x09, 0x09, 0x01, 0x3E, 0x41, 0x41, 0x51, 0x73, 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x41, 0x7F, 0x41, 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01, 0x7F, 0x08, 0x14, 0x22, 0x41, 0x7F, 0x40, 0x40, 0x40, 0x40, 0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x7F, 0x09, 0x09, 0x09, 0x06, 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x7F, 0x09, 0x19, 0x29, 0x46, 0x26, 0x49, 0x49, 0x49, 0x32, 0x03, 0x01, 0x7F, 0x01, 0x03, 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x63, 0x14, 0x08, 0x14, 0x63, 0x03, 0x04, 0x78, 0x04, 0x03, 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00, 0x7F, 0x41, 0x41, 0x41, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x41, 0x41, 0x41, 0x7F, 0x04, 0x02, 0x01, 0x02, 0x04, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x03, 0x07, 0x08, 0x00, 0x20, 0x54, 0x54, 0x78, 0x40, 0x7F, 0x28, 0x44, 0x44, 0x38, 0x38, 0x44, 0x44, 0x44, 0x28, 0x38, 0x44, 0x44, 0x28, 0x7F, 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, 0x08, 0x7E, 0x09, 0x02, 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, 0x44, 0x7D, 0x40, 0x00, 0x20, 0x40, 0x40, 0x3D, 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00, 0x41, 0x7F, 0x40, 0x00, 0x7C, 0x04, 0x78, 0x04, 0x78, 0x7C, 0x08, 0x04, 0x04, 0x78, 0x38, 0x44, 0x44, 0x44, 0x38, 0xFC, 0x18, 0x24, 0x24, 0x18, 0x18, 0x24, 0x24, 0x18, 0xFC, 0x7C, 0x08, 0x04, 0x04, 0x08, 0x48, 0x54, 0x54, 0x54, 0x24, 0x04, 0x04, 0x3F, 0x44, 0x24, 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x44, 0x28, 0x10, 0x28, 0x44, 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x41, 0x36, 0x08, 0x00, 0x02, 0x01, 0x02, 0x04, 0x02, 0x3C, 0x26, 0x23, 0x26, 0x3C, 0x1E, 0xA1, 0xA1, 0x61, 0x12, 0x3A, 0x40, 0x40, 0x20, 0x7A, 0x38, 0x54, 0x54, 0x55, 0x59, 0x21, 0x55, 0x55, 0x79, 0x41, 0x22, 0x54, 0x54, 0x78, 0x42, 0x21, 0x55, 0x54, 0x78, 0x40, 0x20, 0x54, 0x55, 0x79, 0x40, 0x0C, 0x1E, 0x52, 0x72, 0x12, 0x39, 0x55, 0x55, 0x55, 0x59, 0x39, 0x54, 0x54, 0x54, 0x59, 0x39, 0x55, 0x54, 0x54, 0x58, 0x00, 0x00, 0x45, 0x7C, 0x41, 0x00, 0x02, 0x45, 0x7D, 0x42, 0x00, 0x01, 0x45, 0x7C, 0x40, 0x7D, 0x12, 0x11, 0x12, 0x7D, 0xF0, 0x28, 0x25, 0x28, 0xF0, 0x7C, 0x54, 0x55, 0x45, 0x00, 0x20, 0x54, 0x54, 0x7C, 0x54, 0x7C, 0x0A, 0x09, 0x7F, 0x49, 0x32, 0x49, 0x49, 0x49, 0x32, 0x3A, 0x44, 0x44, 0x44, 0x3A, 0x32, 0x4A, 0x48, 0x48, 0x30, 0x3A, 0x41, 0x41, 0x21, 0x7A, 0x3A, 0x42, 0x40, 0x20, 0x78, 0x00, 0x9D, 0xA0, 0xA0, 0x7D, 0x3D, 0x42, 0x42, 0x42, 0x3D, 0x3D, 0x40, 0x40, 0x40, 0x3D, 0x3C, 0x24, 0xFF, 0x24, 0x24, 0x48, 0x7E, 0x49, 0x43, 0x66, 0x2B, 0x2F, 0xFC, 0x2F, 0x2B, 0xFF, 0x09, 0x29, 0xF6, 0x20, 0xC0, 0x88, 0x7E, 0x09, 0x03, 0x20, 0x54, 0x54, 0x79, 0x41, 0x00, 0x00, 0x44, 0x7D, 0x41, 0x30, 0x48, 0x48, 0x4A, 0x32, 0x38, 0x40, 0x40, 0x22, 0x7A, 0x00, 0x7A, 0x0A, 0x0A, 0x72, 0x7D, 0x0D, 0x19, 0x31, 0x7D, 0x26, 0x29, 0x29, 0x2F, 0x28, 0x26, 0x29, 0x29, 0x29, 0x26, 0x30, 0x48, 0x4D, 0x40, 0x20, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x38, 0x2F, 0x10, 0xC8, 0xAC, 0xBA, 0x2F, 0x10, 0x28, 0x34, 0xFA, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x08, 0x14, 0x2A, 0x14, 0x22, 0x22, 0x14, 0x2A, 0x14, 0x08, 0x55, 0x00, 0x55, 0x00, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0xFF, 0x55, 0xFF, 0x55, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x10, 0x10, 0x10, 0xFF, 0x00, 0x14, 0x14, 0x14, 0xFF, 0x00, 0x10, 0x10, 0xFF, 0x00, 0xFF, 0x10, 0x10, 0xF0, 0x10, 0xF0, 0x14, 0x14, 0x14, 0xFC, 0x00, 0x14, 0x14, 0xF7, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x14, 0x14, 0xF4, 0x04, 0xFC, 0x14, 0x14, 0x17, 0x10, 0x1F, 0x10, 0x10, 0x1F, 0x10, 0x1F, 0x14, 0x14, 0x14, 0x1F, 0x00, 0x10, 0x10, 0x10, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x10, 0x10, 0x10, 0x10, 0x1F, 0x10, 0x10, 0x10, 0x10, 0xF0, 0x10, 0x00, 0x00, 0x00, 0xFF, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xFF, 0x14, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x1F, 0x10, 0x17, 0x00, 0x00, 0xFC, 0x04, 0xF4, 0x14, 0x14, 0x17, 0x10, 0x17, 0x14, 0x14, 0xF4, 0x04, 0xF4, 0x00, 0x00, 0xFF, 0x00, 0xF7, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0xF7, 0x00, 0xF7, 0x14, 0x14, 0x14, 0x17, 0x14, 0x10, 0x10, 0x1F, 0x10, 0x1F, 0x14, 0x14, 0x14, 0xF4, 0x14, 0x10, 0x10, 0xF0, 0x10, 0xF0, 0x00, 0x00, 0x1F, 0x10, 0x1F, 0x00, 0x00, 0x00, 0x1F, 0x14, 0x00, 0x00, 0x00, 0xFC, 0x14, 0x00, 0x00, 0xF0, 0x10, 0xF0, 0x10, 0x10, 0xFF, 0x10, 0xFF, 0x14, 0x14, 0x14, 0xFF, 0x14, 0x10, 0x10, 0x10, 0x1F, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x38, 0x44, 0x44, 0x38, 0x44, 0xFC, 0x4A, 0x4A, 0x4A, 0x34, 0x7E, 0x02, 0x02, 0x06, 0x06, 0x02, 0x7E, 0x02, 0x7E, 0x02, 0x63, 0x55, 0x49, 0x41, 0x63, 0x38, 0x44, 0x44, 0x3C, 0x04, 0x40, 0x7E, 0x20, 0x1E, 0x20, 0x06, 0x02, 0x7E, 0x02, 0x02, 0x99, 0xA5, 0xE7, 0xA5, 0x99, 0x1C, 0x2A, 0x49, 0x2A, 0x1C, 0x4C, 0x72, 0x01, 0x72, 0x4C, 0x30, 0x4A, 0x4D, 0x4D, 0x30, 0x30, 0x48, 0x78, 0x48, 0x30, 0xBC, 0x62, 0x5A, 0x46, 0x3D, 0x3E, 0x49, 0x49, 0x49, 0x00, 0x7E, 0x01, 0x01, 0x01, 0x7E, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x44, 0x44, 0x5F, 0x44, 0x44, 0x40, 0x51, 0x4A, 0x44, 0x40, 0x40, 0x44, 0x4A, 0x51, 0x40, 0x00, 0x00, 0xFF, 0x01, 0x03, 0xE0, 0x80, 0xFF, 0x00, 0x00, 0x08, 0x08, 0x6B, 0x6B, 0x08, 0x36, 0x12, 0x36, 0x24, 0x36, 0x06, 0x0F, 0x09, 0x0F, 0x06, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x30, 0x40, 0xFF, 0x01, 0x01, 0x00, 0x1F, 0x01, 0x01, 0x1E, 0x00, 0x19, 0x1D, 0x17, 0x12, 0x00, 0x3C, 0x3C, 0x3C, 0x3C ]; ================================================ FILE: dlib/image/resampling/bicubic.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Bicubic resampling * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.resampling.bicubic; import std.math; import dlib.image.image; import dlib.image.color; T bicubic(T) (T x) { if (x > 2.0) return 0.0; T a, b, c, d; T xm1 = x - 1.0; T xp1 = x + 1.0; T xp2 = x + 2.0; a = ( xp2 <= 0.0 ) ? 0.0 : xp2 * xp2 * xp2; b = ( xp1 <= 0.0 ) ? 0.0 : xp1 * xp1 * xp1; c = ( x <= 0.0 ) ? 0.0 : x * x * x; d = ( xm1 <= 0.0 ) ? 0.0 : xm1 * xm1 * xm1; return ( 0.16666666666666666667 * ( a - ( 4.0 * b ) + ( 6.0 * c ) - ( 4.0 * d ) ) ); } /// Resize image with bicubic filter SuperImage resampleBicubic(SuperImage img, uint newWidth, uint newHeight) in { assert (img.data.length); } do { SuperImage res = img.createSameFormat(newWidth, newHeight); float xFactor = cast(float)img.width / cast(float)newWidth; float yFactor = cast(float)img.height / cast(float)newHeight; foreach(x; 0..res.width) { float ox = x * xFactor - 0.5f; int ox1 = cast(int)ox; float dx = ox - ox1; foreach(y; 0..res.height) { float oy = y * yFactor - 0.5f; int oy1 = cast(int)oy; float dy = oy - oy1; Color4f colSum = Color4f(0, 0, 0); foreach(kx; -1..3) { int ix = ox1 + kx; if (ix < 0) ix = 0; if (ix >= img.width) ix = img.width - 1; foreach(ky; -1..3) { int iy = oy1 + ky; if (iy < 0) iy = 0; if (iy >= img.height) iy = img.height - 1; auto col = img[ix, iy]; float k1 = bicubic(dy - cast(float)ky); float k2 = k1 * bicubic(cast(float)kx - dx); colSum += col * k2; } } res[x, y] = colSum; } } return res; } ================================================ FILE: dlib/image/resampling/bilinear.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Bilinear resampling * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.resampling.bilinear; import std.math; import dlib.image.image; import dlib.image.color; /// Resize image with bilinear filter SuperImage resampleBilinear(SuperImage img, in uint newWidth, in uint newHeight) in { assert (img.data.length); } do { SuperImage res = img.createSameFormat(newWidth, newHeight); float xFactor = cast(float)img.width / cast(float)newWidth; float yFactor = cast(float)img.height / cast(float)newHeight; int floor_x, floor_y, ceil_x, ceil_y; float fraction_x, fraction_y, one_minus_x, one_minus_y; Color4f c1, c2, c3, c4; Color4f col; float b1, b2; foreach(y; 0..res.height) foreach(x; 0..res.width) { floor_x = cast(int)floor(x * xFactor); floor_y = cast(int)floor(y * yFactor); ceil_x = floor_x + 1; if (ceil_x >= img.width) ceil_x = floor_x; ceil_y = floor_y + 1; if (ceil_y >= img.height) ceil_y = floor_y; fraction_x = x * xFactor - floor_x; fraction_y = y * yFactor - floor_y; one_minus_x = 1.0f - fraction_x; one_minus_y = 1.0f - fraction_y; c1 = img[floor_x, floor_y]; c2 = img[ceil_x, floor_y]; c3 = img[floor_x, ceil_y]; c4 = img[ceil_x, ceil_y]; // Red b1 = one_minus_x * c1.r + fraction_x * c2.r; b2 = one_minus_x * c3.r + fraction_x * c4.r; col.r = one_minus_y * b1 + fraction_y * b2; // Green b1 = one_minus_x * c1.g + fraction_x * c2.g; b2 = one_minus_x * c3.g + fraction_x * c4.g; col.g = one_minus_y * b1 + fraction_y * b2; // Blue b1 = one_minus_x * c1.b + fraction_x * c2.b; b2 = one_minus_x * c3.b + fraction_x * c4.b; col.b = one_minus_y * b1 + fraction_y * b2; // Alpha b1 = one_minus_x * c1.a + fraction_x * c2.a; b2 = one_minus_x * c3.a + fraction_x * c4.a; col.a = one_minus_y * b1 + fraction_y * b2; res[x, y] = col; } return res; } ================================================ FILE: dlib/image/resampling/lanczos.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Lanczos resampling * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.resampling.lanczos; import std.math; import dlib.image.image; import dlib.image.color; T lanczos(T) (T x, int filterSize) { if (x <= -filterSize || x >= filterSize) return 0.0; // Outside of the window if (x > -T.epsilon && x < T.epsilon) return 1.0; // Special case the discontinuity at the origin auto sinc = (T x) => sin(PI * x) / (PI * x); return sinc(x) * sinc(x / filterSize); } /// Resize image with Lanczos filter SuperImage resampleLanczos(SuperImage img, in uint newWidth, in uint newHeight) in { assert (img.data.length); } do { SuperImage res = img.createSameFormat(newWidth, newHeight); float xFactor = cast(float)img.width / cast(float)newWidth; float yFactor = cast(float)img.height / cast(float)newHeight; foreach(x; 0..res.width) { float ox = x * xFactor - 0.5f; int ox1 = cast(int)ox; float dx = ox - ox1; foreach(y; 0..res.height) { float oy = y * yFactor - 0.5f; int oy1 = cast(int)oy; float dy = oy - oy1; Color4f colSum = Color4f(0, 0, 0); float kSum; foreach(kx; -3..4) { int ix = ox1 + kx; if (ix < 0) ix = 0; if (ix >= img.width) ix = img.width - 1; foreach(ky; -3..4) { int iy = oy1 + ky; if (iy < 0) iy = 0; if (iy >= img.height) iy = img.height - 1; auto col = img[ix, iy]; float k1 = lanczos((cast(float)ky - dy), 3); float k2 = k1 * lanczos((cast(float)kx - dx), 3); kSum += k2; colSum += col * k2; } } if (kSum > 0.0f) colSum /= kSum; res[x, y] = colSum; } } return res; } ================================================ FILE: dlib/image/resampling/nearest.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Nearest-neighbor resampling * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.resampling.nearest; import dlib.image.image; import dlib.image.color; /// Resize image with nearest-neighbor filter SuperImage resampleNearestNeighbor(SuperImage img, in uint newWidth, in uint newHeight) in { assert (img.data.length); } do { SuperImage res = img.createSameFormat(newWidth, newHeight); float scaleWidth = cast(float)newWidth / cast(float)img.width; float scaleHeight = cast(float)newHeight / cast(float)img.height; uint nearest_x, nearest_y; foreach(y; 0..res.height) foreach(x; 0..res.width) { nearest_x = cast(uint)(x / scaleWidth); nearest_y = cast(uint)(y / scaleHeight); res[x, y] = img[nearest_x, nearest_y]; } return res; } ================================================ FILE: dlib/image/resampling/package.d ================================================ /* Copyright (c) 2022-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Image resizing algorithms * * Copyright: Timur Gafarov 2022-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.resampling; public { import dlib.image.resampling.nearest; import dlib.image.resampling.bilinear; import dlib.image.resampling.bicubic; import dlib.image.resampling.lanczos; } ================================================ FILE: dlib/image/signal2d.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * 2D signal consisting of complex number data * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.signal2d; private { import dlib.core.memory; import dlib.math.vector; import dlib.math.complex; import dlib.math.fft; import dlib.image.image; import dlib.image.color; } /// Signal domain enum SignalDomain { Spatial, Frequency } /** * 2D signal consisting of complex number data */ class Signal2D { Complexf[] data; uint width; uint height; SignalDomain domain = SignalDomain.Spatial; this(uint w, uint h) { width = w; height = h; data = New!(Complexf[])(width * height); } this(SuperImage img, uint channel) { width = img.width; height = img.height; data = New!(Complexf[])(width * height); foreach(y; 0..height) foreach(x; 0..width) { auto col = img[x, y]; size_t index = (y * width + x); data[index].re = col.vec[channel]; data[index].im = 0.0f; } } ~this() { Delete(data); } void fft() { foreach(y; 0..height) foreach(x; 0..width) { if (((x + y) & 0x1) != 0) { size_t index = (y * width + x); data[index].re = data[index].re * -1; data[index].im = data[index].im * -1; } } fft2(true); domain = SignalDomain.Frequency; } void fftInverse() { if (domain != SignalDomain.Frequency) return; fft2(false); domain = SignalDomain.Spatial; foreach(y; 0..height) foreach(x; 0..width) { if (((x + y) & 0x1) != 0) { size_t index = (y * width + x); data[index].re = data[index].re * -1; data[index].im = data[index].im * -1; } } } void multiply(Signal2D img, Signal2D dest) { assert(img.width == width && img.height == height && dest.width == width && dest.height == height); dest.domain = domain; foreach(i, v; data) { dest.data[i] = v * img.data[i]; } } void divide(Signal2D img, Signal2D dest) { assert(img.width == width && img.height == height && dest.width == width && dest.height == height); dest.domain = domain; foreach(i, v; data) { if (img.data[i].re == 0.0f) dest.data[i] = v; else dest.data[i] = v / img.data[i]; } } Complexf opIndex(int x, int y) { while(x >= width) x = width-1; while(y >= height) y = height-1; while(x < 0) x = 0; while(y < 0) y = 0; return data[y * width + x]; } void fft2(bool forward) { // process rows Complexf[] row = New!(Complexf[])(width); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { auto index = y * width + x; row[x] = data[index]; } fastFourierTransform(row, forward); for (int x = 0; x < width; x++) { auto index = y * width + x; data[index] = row[x]; } } // process columns Complexf[] col = New!(Complexf[])(height); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { auto index = y * width + x; col[y] = data[index]; } fastFourierTransform(col, forward); for (int y = 0; y < height; y++) { auto index = y * width + x; data[index] = col[y]; } } Delete(row); Delete(col); } } /// Convert Signal2D to SuperImage void signalToImage(Signal2D chRed, Signal2D chGreen, Signal2D chBlue, SuperImage img) { float maxIntensityR = 0.0f; float maxIntensityG = 0.0f; float maxIntensityB = 0.0f; Vector3f[] fpImage = New!(Vector3f[])(img.width * img.height); for(int i = 0; i < chRed.data.length; i++) { float mr, mg, mb; mr = chRed.data[i].magnitude; if (mr > maxIntensityR) maxIntensityR = mr; mg = chGreen.data[i].magnitude; if (mg > maxIntensityG) maxIntensityG = mg; mb = chBlue.data[i].magnitude; if (mb > maxIntensityB) maxIntensityB = mb; fpImage[i] = Vector3f(mr, mg, mb); } foreach(ref v; fpImage) { v.r /= maxIntensityR; v.g /= maxIntensityG; v.b /= maxIntensityB; } foreach(y; 0..img.height) foreach(x; 0..img.width) { size_t index = (y * img.width + x); Vector3f m = fpImage[index]; img[x, y] = Color4f(m); } Delete(fpImage); } ================================================ FILE: dlib/image/transform.d ================================================ /* Copyright (c) 2016-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Geometric ransformations of images * * Copyright: Timur Gafarov 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.transform; import dlib.math.vector; import dlib.math.matrix; import dlib.math.transformation; import dlib.math.utils; import dlib.image.image; import dlib.image.color; /// Tranforms an image with affine 3x3 matrix SuperImage affineTransformImage(SuperImage img, SuperImage outp, Matrix3x3f m) { SuperImage res; if (outp) res = outp; else res = img.createSameFormat(img.width, img.height); foreach(y; 0..res.height) foreach(x; 0..res.width) { Vector2f v1 = Vector2f(x, y).affineTransform2D(m); res[x, y] = bilinearPixel(img, v1.x, v1.y); } return res; } /// ditto SuperImage affineTransformImage(SuperImage img, Matrix3x3f m) { return affineTransformImage(img, null, m); } /// Translates an image (positive x goes right, positive y goes down) SuperImage translateImage(SuperImage img, SuperImage outp, Vector2f t) { Matrix3x3f m = translationMatrix2D(-t); return affineTransformImage(img, outp, m); } /// ditto SuperImage translateImage(SuperImage img, Vector2f t) { return translateImage(img, null, t); } /// Rotates an image clockwise around its center. Angle is in degrees. SuperImage rotateImage(SuperImage img, SuperImage outp, float angle) { Vector2f center = Vector2f(img.width, img.height) * 0.5f; Matrix3x3f m = translationMatrix2D(center) * rotationMatrix2D(degtorad(angle)) * translationMatrix2D(-center); return affineTransformImage(img, outp, m); } /// ditto SuperImage rotateImage(SuperImage img, float angle) { return rotateImage(img, null, angle); } /// Scales an image SuperImage scaleImage(SuperImage img, SuperImage outp, Vector2f s) { Matrix3x3f m = scaleMatrix2D(Vector2f(1, 1) / s); return affineTransformImage(img, outp, m); } /// ditto SuperImage scaleImage(SuperImage img, Vector2f s) { return scaleImage(img, null, s); } /// Uniformly scales an image SuperImage scaleImage(SuperImage img, SuperImage outp, float s) { float sinv = 1.0f / s; Matrix3x3f m = scaleMatrix2D(Vector2f(sinv, sinv)); return affineTransformImage(img, outp, m); } /// ditto SuperImage scaleImage(SuperImage img, float s) { return scaleImage(img, null, s); } ================================================ FILE: dlib/image/unmanaged.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * GC-free SuperImage implementation * * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.image.unmanaged; import dlib.image.image; import dlib.core.memory; /** * Image that uses dlib.core.memory instead of GC */ class UnmanagedImage(IntegerPixelFormat fmt): Image!(fmt) { override @property SuperImage dup() { auto res = New!(UnmanagedImage!(fmt))(_width, _height); res.data[] = data[]; return res; } override SuperImage createSameFormat(uint w, uint h) { return New!(UnmanagedImage!(fmt))(w, h); } this(uint w, uint h) { super(w, h); } ~this() { Delete(_data); } protected override void allocateData() { _data = New!(ubyte[])(_width * _height * _pixelSize); } override void free() { Delete(this); } } /// Specialization of UnmanagedImage for 8-bit luminance pixel format alias UnmanagedImageL8 = UnmanagedImage!(IntegerPixelFormat.L8); /// Specialization of UnmanagedImage for 8-bit luminance-alpha pixel format alias UnmanagedImageLA8 = UnmanagedImage!(IntegerPixelFormat.LA8); /// Specialization of UnmanagedImage for 8-bit RGB pixel format alias UnmanagedImageRGB8 = UnmanagedImage!(IntegerPixelFormat.RGB8); /// Specialization of UnmanagedImage for 8-bit RGBA pixel format alias UnmanagedImageRGBA8 = UnmanagedImage!(IntegerPixelFormat.RGBA8); /// Specialization of UnmanagedImage for 16-bit luminance pixel format alias UnmanagedImageL16 = UnmanagedImage!(IntegerPixelFormat.L16); /// Specialization of UnmanagedImage for 16-bit luminance-alpha pixel format alias UnmanagedImageLA16 = UnmanagedImage!(IntegerPixelFormat.LA16); /// Specialization of UnmanagedImage for 16-bit RGB pixel format alias UnmanagedImageRGB16 = UnmanagedImage!(IntegerPixelFormat.RGB16); /// Specialization of UnmanagedImage for 16-bit RGBA pixel format alias UnmanagedImageRGBA16 = UnmanagedImage!(IntegerPixelFormat.RGBA16); /** * UnmanagedImage factory class */ class UnmanagedImageFactory: SuperImageFactory { SuperImage createImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1) { return unmanagedImage(w, h, channels, bitDepth); } } /** * UnmanagedImage factory function */ SuperImage unmanagedImage(uint w, uint h, uint channels = 3, uint bitDepth = 8) in { assert(channels > 0 && channels <= 4); assert(bitDepth == 8 || bitDepth == 16); } do { switch(channels) { case 1: { if (bitDepth == 8) return New!UnmanagedImageL8(w, h); else return New!UnmanagedImageL16(w, h); } case 2: { if (bitDepth == 8) return New!UnmanagedImageLA8(w, h); else return New!UnmanagedImageLA16(w, h); } case 3: { if (bitDepth == 8) return New!UnmanagedImageRGB8(w, h); else return New!UnmanagedImageRGB16(w, h); } case 4: { if (bitDepth == 8) return New!UnmanagedImageRGBA8(w, h); else return New!UnmanagedImageRGBA16(w, h); } default: assert(0); } } ================================================ FILE: dlib/math/combinatorics.d ================================================ /* Copyright (c) 2015-2025 Nick Papanastasiou, Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Combinatorics * * Copyright: Nick Papanastasiou 2015-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Nick Papanastasiou, Timur Gafarov */ module dlib.math.combinatorics; import std.functional: memoize; import std.algorithm: reduce, map; import std.range: iota; import std.bigint; /// Returns the factorial of n ulong factorial(ulong n) @safe nothrow { if(n <= 1) { return 1; } alias mfac = memoize!factorial; return n * mfac(n - 1); } /// unittest { assert(factorial(10) == 3_628_800); int n = 5; assert(n.factorial == 5.factorial && 5.factorial == 120); } /// Computes the nth fibonacci number ulong fibonacci(ulong n) { if(n == 0 || n == 1) { return n; } alias mfib = memoize!fibonacci; return mfib(n - 1) + mfib(n - 2); } /// Common vernacular for fibonacci alias fib = fibonacci; /// unittest { import std.array: array; auto fibs = iota(1, 21).map!(n => fib(n)).array; assert(fibs == [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]); } /// Computes the double factorial of n: n * (n - 2) * (n - 4) * ... * 1 ulong doubleFactorial(ulong n) { if (n <= 1) { return 1; } alias mDoubleFac = memoize!doubleFactorial; return n * doubleFactorial(n - 2); } /// unittest { import std.array: array; auto dfacs = iota(1, 21).map!(n => doubleFactorial(n)).array; assert(dfacs == [1, 2, 3, 8, 15, 48, 105, 384, 945, 3840, 10395, 46080, 135135, 645120, 2027025, 10321920, 34459425, 185794560, 654729075, 3715891200]); } /// Computes the hyperfactorial of n: 1^1 * 2^2 * 3^3 * ... n^n BigInt hyperFactorial(ulong n) { if(n <= 1) { return BigInt("1"); } alias mhfac = memoize!hyperFactorial; return BigInt(n ^^ n) * hyperFactorial(n - 1); } /// unittest { import std.array: array; auto hfacs = iota(1, 6).map!(n => hyperFactorial(n)).array; assert(hfacs == [1, 4, 108, 27648, 86400000]); } /++ + Compute the number of combinations of `objects` types of items + when considered `taken` at a time, where order is ignored +/ ulong combinations(ulong objects, ulong taken) @safe nothrow { if (objects < taken) { return 0; } return objects.factorial / (taken.factorial * (objects - taken).factorial); } /// Common vernacular for combinations alias C = combinations; /// Ditto alias choose = combinations; /// unittest { assert(1.choose(2) == 0); assert(5.choose(2) == 10); } /++ + Compute the number of permutations of `objects` types of items + when considered `taken` at a time, where order is considered +/ ulong permutations(ulong objects, ulong taken) @safe nothrow { return objects.factorial / (objects - taken).factorial; } // Common vernacular for permutations alias P = permutations; /// unittest { assert(10.P(2) == 90); } /// Computes the nth Lucas number ulong lucas(ulong n) @safe nothrow { if (n == 0) { return 2; } if (n == 1) { return 1; } alias mlucas = memoize!lucas; return mlucas(n - 1) + mlucas(n - 2); } unittest { import std.algorithm: map; import std.array; auto lucasRange = iota(0, 12).map!(k => lucas(k)).array; assert(lucasRange == [2, 1, 3, 4, 7, 11, 18, 29, 47, 76, 123, 199]); } ================================================ FILE: dlib/math/complex.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Complex numbers * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.complex; import std.math; import std.range; import std.format; /// Complex number representation struct Complex(T) { T re; T im; this(T r, T i) { re = r; im = i; } this(T r) { re = r; im = 0.0; } Complex!(T) opUnary(string op) () if (op == "-") { return Complex!(T)(re, -im); } Complex!(T) opBinary(string op)(Complex!(T) c) if (op == "+") { return Complex!(T)(re + c.re, im + c.im); } Complex!(T) opBinary(string op)(Complex!(T) c) if (op == "-") { return Complex!(T)(re - c.re, im - c.im); } Complex!(T) opBinary(string op)(Complex!(T) c) if (op == "*") { return Complex!(T)( re * c.re - im * c.im, re * c.im + im * c.re); } Complex!(T) opBinary(string op)(Complex!(T) c) if (op == "/") { T denominator = c.re * c.re + c.im * c.im; return Complex!(T)( (re * c.re + im * c.im) / denominator, (im * c.re - re * c.im) / denominator); } Complex!(T) opOpAssign(string op)(Complex!(T) c) if (op == "+") { re += c.re; im += c.im; return this; } Complex!(T) opOpAssign(string op)(Complex!(T) c) if (op == "-") { re -= c.re; im -= c.im; return this; } Complex!(T) opOpAssign(string op)(Complex!(T) c) if (op == "*") { T temp = re; re = re * c.re - im * c.im; im = im * c.re + temp * c.im; return this; } Complex!(T) opOpAssign(string op)(Complex!(T) c) if (op == "/") { T denominator = c.re * c.re + c.im * c.im; T temp = re; re = (re * c.re + im * c.im) / denominator; im = (im * c.re - temp * c.im) / denominator; return this; } Complex!(T) opBinary(string op)(T scalar) if (op == "+") { return Complex!(T)(re + scalar, im + scalar); } Complex!(T) opBinary(string op)(T scalar) if (op == "-") { return Complex!(T)(re + scalar, im + scalar); } Complex!(T) opBinary(string op)(T scalar) if (op == "*") { return Complex!(T)(re * scalar, im * scalar); } Complex!(T) opBinary(string op)(T scalar) if (op == "/") { return Complex!(T)(re / scalar, im / scalar); } Complex!(T) reciprocal() { T scale = re * re + im * im; return Complex!(T)(re / scale, -im / scale); } T magnitude() { return sqrt(re * re + im * im); } T norm() { return (re * re + im * im); } string toString() { auto writer = appender!string(); formattedWrite(writer, "%s + %si", re, im); return writer.data; } } /// unittest { import dlib.math.utils; auto c1 = Complex!float(2, 1); auto c2 = Complex!float(1, 2); auto c3 = Complex!float(1); assert((c1 + c2) == Complex!float(3, 3)); assert((c1 - c1) == Complex!float(0, 0)); assert((c1 * c2) == Complex!float(0, 5)); assert((c2 / c1) == Complex!float(0.8f, 0.6f)); assert(c1.reciprocal == Complex!float(0.4f, -0.2f)); assert(c3.magnitude == 1.0f); assert(c3.norm == 1.0f); assert(c1.toString == "2 + 1i"); } /// Complex abs T abs(T)(Complex!T x) { return sqrt(x.re * x.re + x.im * x.im); } /// unittest { auto c1 = Complex!float(1); assert(abs(c1) == 1.0f); } /// Complex atan2 T atan2(T)(Complex!T x) { return std.math.atan2(x.im, x.re); } /// unittest { auto c1 = Complex!float(1); assert(atan2(c1) == 0.0f); } /// Complex pow Complex!T pow(T)(Complex!T x, Complex!T n) { T r = abs(x); T t = x.magnitude; T c = n.re; T d = n.im; Complex!T res; res.re = std.math.pow(r, c) * std.math.exp(-d*t) * cos(c*t + d*log(r)); res.im = std.math.pow(r, c) * std.math.exp(-d*t) * sin(c*t + d*log(r)); return res; } /// Complex exp Complex!T exp(T)(Complex!T s) { return Complex!T( std.math.exp(s.re) * cos(s.im), std.math.exp(s.re) * sin(s.im)); } /* * Predefined complex type aliases */ alias Complexf = Complex!(float); alias Complexd = Complex!(double); ================================================ FILE: dlib/math/decomposition.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Matrix decomposition * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.decomposition; import std.math; import dlib.math.matrix; /** * LUP decomposition. * Factors a matrix A into PA = LU, * where L is a lower triangular matrix, * U is an upper triangular matrix, * and P is a permutation matrix. */ void decomposeLUP(T, size_t N)( Matrix!(T,N) A, ref Matrix!(T,N) L, ref Matrix!(T,N) U, ref Matrix!(T,N) P) { Matrix!(T,N) C = A; P = Matrix!(T,N).identity; for (size_t i = 0; i < N; i++) { T pivotValue = 0.0; size_t pivot; for (size_t row = i; row < N; row++) { if (abs(C[row, i]) > pivotValue) { pivotValue = abs(C[row, i]); pivot = row; } } assert(pivotValue != 0.0); P.swapRows(pivot, i); C.swapRows(pivot, i); for (size_t j = i + 1; j < N; j++) { C[j, i] = C[j, i] / C[i, i]; for (size_t k = i + 1; k < N; k++) C[j, k] = C[j, k] - C[j, i] * C[i, k]; } } L = Matrix!(T,N).identity; U = Matrix!(T,N).zero; for (size_t i = 0; i < N; i++) { for (size_t j = 0; j < N-1-i; j++) L[N-1-i, j] = C[N-1-i, j]; for (size_t j = i; j < N; j++) U[i, j] = C[i, j]; } } ================================================ FILE: dlib/math/diff.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Automatic differentiation * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.diff; import dlib.math.dual; import dlib.core.compound; /** * Differentiate a function of a single argument * Examples: * --- * auto r = diff!f(x); * r[0] // = f(x) * r[1] // = f'(x) * --- */ auto diff(alias F, T)(T x) { auto eval = F(Dual!(T)(x, 1.0)); return compound(eval.re, eval.du); } ================================================ FILE: dlib/math/dual.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Dual numbers * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.dual; private { import std.math; } /** * Dual number representation */ struct Dual(T) { T re; T du; this(T r) { re = r; du = 0.0; } this(T r, T d) { re = r; du = d; } this(in int e) { re = cast(T)e; du = 0.0; } static Dual!(T) opCast(in T x) { return Dual!(T)(x, 0.0); } Dual!(T) opAssign(in T x) { re = x; du = 0.0; return this; } Dual!(T) opBinary(string op)(in T x) const if (op == "+") { return this + Dual!(T)(x); } Dual!(T) opBinary(string op)(in T x) const if (op == "-") { return this - Dual!(T)(x); } Dual!(T) opBinary(string op)(in T x) const if (op == "*") { return this * Dual!(T)(x); } Dual!(T) opBinary(string op)(in T x) const if (op == "/") { return this / Dual!(T)(x); } Dual!(T) opUnary(string s)() const if (s == "-") { return Dual!(T)(-re, -du); } Dual!(T) opBinary(string op)(in Dual!(T) x) const if (op == "+") { return Dual!(T)( re + x.re, du + x.du ); } Dual!(T) opBinary(string op)(in Dual!(T) x) const if (op == "-") { return Dual!(T)( re - x.re, du - x.du ); } Dual!(T) opBinary(string op)(in Dual!(T) x) const if (op == "*") { return Dual!(T)( re * x.re, re * x.du + du * x.re ); } Dual!(T) opBinary(string op)(in Dual!(T) x) const if (op == "/") { return Dual!(T)( re / x.re, (re * x.du - du * x.re) / (x.re * x.re) ); } Dual!(T) opOpAssign(string op)(in Dual!(T) x) if (op == "+") { re += x.re; du += x.du; return this; } Dual!(T) opOpAssign(string op)(in Dual!(T) x) if (op == "-") { re -= x.re; du -= x.du; return this; } Dual!(T) opOpAssign(string op)(in Dual!(T) x) if (op == "*") { re *= x.re, du = re * x.du + du * x.re; return this; } Dual!(T) opOpAssign(string op)(in Dual!(T) x) if (op == "/") { re /= x.re, du = (re * x.du - du * x.re) / (x.re * x.re); return this; } Dual!(T) sqrt() const { T tmp = std.math.sqrt(re); return Dual!(T)( tmp, du / (2.0 * tmp) ); } Dual!(T) opBinaryRight(string op)(in T v) const if (op == "+") { return Dual!(T)(v) + this; } Dual!(T) opBinaryRight(string op)(in T v) const if (op == "-") { return Dual!(T)(v) - this; } Dual!(T) opBinaryRight(string op)(in T v) const if (op == "*") { return Dual!(T)(v) * this; } Dual!(T) opBinaryRight(string op)(in T v) const if (op == "/") { return Dual!(T)(v) / this; } int opCmp(in double s) const { return cast(int)((re - s) * 100); } Dual!(T) sin() const { return Dual!(T)(.sin(re), du * .cos(re)); } Dual!(T) cos() const { return Dual!(T)(.cos(re), -du * .sin(re)); } Dual!(T) exp() const { return Dual!(T)(.exp(re), du * .exp(re)); } Dual!(T) pow(T k) const { return Dual!(T)(re^^k, k * (re^^(k-1)) * du); } } /// Alias for single precision Dual specialization alias Dualf = Dual!(float); /// Alias for double precision Dual specialization alias Duald = Dual!(double); /// unittest { Dualf a = Dualf(1.0f, 1.0f); assert(a + a == Dualf(2.0f, 2.0f)); assert(a - a == Dualf(0.0f, 0.0f)); assert(a * a == Dualf(1.0f, 2.0f)); assert(a / a == Dualf(1.0f, 0.0f)); assert(Dualf(4.0f, 1.0f).sqrt == Dualf(2.0f, 0.25f)); } ================================================ FILE: dlib/math/dualquaternion.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Dual quaternions * * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.dualquaternion; import std.math; import std.range; import std.format; import dlib.math.vector; import dlib.math.matrix; import dlib.math.quaternion; import dlib.math.transformation; import dlib.math.dual; /** * Dual quaternion representation. * Dual quaternion is a generalization of quaternion to dual numbers field. * Similar to the way that simple quaternion represents rotation in 3D space, * dual quaternion represents rigid 3D transformation (translation + rotation), * so it can be used in kinematics. */ struct DualQuaternion(T) { this(Quaternion!(T) q1, Quaternion!(T) q2) { this.q1 = q1; this.q2 = q2; } this(Quaternion!(T) r, Vector!(T,3) t) { this.q1 = r; this.q2 = Quaternion!(T)(t * 0.5, 0.0) * r; } this(Quaternion!(T) r) { this.q1 = r; this.q2 = Quaternion!(T).identity * r; } this(Vector!(T,3) t) { this.q1 = Quaternion!(T).identity; this.q2 = Quaternion!(T)(t * 0.5, 0.0); } Vector!(T,3) transform(Vector!(T,3) v) { auto vq = DualQuaternion!(T)( Quaternion!(T).identity, Quaternion!(T)(v.x, v.y, v.z, 0.0)); auto q = this * vq * this.fullConjugate; return q.q2.xyz; } Vector!(T,3) rotate(Vector!(T,3) v) { return q1.rotate(v); } DualQuaternion!(T) conjugate() { return DualQuaternion!(T)(q1.conj, q2.conj); } DualQuaternion!(T) dualConjugate() { return DualQuaternion!(T)(q1, q2 * -1.0); } DualQuaternion!(T) fullConjugate() { return DualQuaternion!(T)(q1.conj, q2.conj * -1.0); } DualQuaternion!(T) opBinary(string op)(DualQuaternion!(T) d) if (op == "*") { return DualQuaternion!(T)(q1 * d.q1, q1 * d.q2 + q2 * d.q1); } DualQuaternion!(T) opBinary(string op)(DualQuaternion!(T) d) if (op == "+") { return DualQuaternion!(T)(q1 + d.q1, q2 + d.q2); } DualQuaternion!(T) opBinary(string op)(DualQuaternion!(T) d) if (op == "-") { return DualQuaternion!(T)(q1 - d.q1, q2 - d.q2); } /** * Rotation part */ Quaternion!(T) rotation() { return q1; } /** * Translation part */ Vector!(T,3) translation() { return (2.0 * q2 * q1.conj).xyz; } /** * Convert to 4x4 matrix */ Matrix!(T,4) toMatrix4x4() { // TODO: Can this be done without matrix multiplication? return translationMatrix(translation) * rotation.toMatrix4x4; } /** * Dual quaternion norm */ Dual!(T) norm() { auto qq = this * this.conjugate; return Dual!(T)(qq.q1.lengthsqr, qq.q2.lengthsqr).sqrt; } /** * Set norm to 1 */ DualQuaternion!(T) normalized() { Dual!(T) n = norm; return DualQuaternion!(T)(q1 / n.re, q2 / n.re); } /** * Convert to string */ string toString() { auto writer = appender!string(); formattedWrite(writer, "[%s, %s]", q1.arrayof, q2.arrayof); return writer.data; } /** * Elements union */ union { struct { /// Rotation part Quaternion!(T) q1; /// Translation part Quaternion!(T) q2; } /// Elements as static array T[8] arrayof; } } /// Alias for single precision DualQuaternion specialization alias DualQuaternionf = DualQuaternion!(float); /// Alias for double precision DualQuaternion specialization alias DualQuaterniond = DualQuaternion!(double); /// unittest { Quaternionf r1 = rotationQuaternion!float(0, PI * 0.5f); Vector3f t1 = Vector3f(1.0f, 0.0f, 0.0f); DualQuaternionf dq1 = DualQuaternionf(r1, t1); assert(dq1.rotation == r1); assert(isAlmostZero(dq1.translation - t1)); Vector3f v = dq1.rotate(Vector3f(0.0f, 1.0f, 0.0f)); assert(isAlmostZero(v - Vector3f(0.0f, 0.0f, 1.0f))); } ================================================ FILE: dlib/math/fft.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Fast Fourier transform * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.fft; import std.math; import dlib.math.utils; import dlib.math.complex; /// Forward or backward fast Fourier transform. Data must be power of two in length void fastFourierTransform(Complexf[] data, bool forward) { assert(isPowerOfTwo(data.length)); uint target = 0; for (uint pos = 0; pos < data.length; ++pos) { if (target > pos) { Complexf temp = data[target]; data[target] = data[pos]; data[pos] = temp; } uint mask = cast(uint)data.length; while (target & (mask >>= 1)) target &= ~mask; target |= mask; } float pi = forward? -PI : PI; for (uint step = 1; step < data.length; step <<= 1) { uint jump = step << 1; float delta = pi / cast(float)step; float sine = sin(delta * 0.5f); Complexf multiplier = Complexf(-2.0f * sine * sine, sin(delta)); Complexf factor = Complexf(1.0f); for (uint group = 0; group < step; ++group) { for (uint pair = group; pair < data.length; pair += jump) { uint match = pair + step; Complexf product = factor * data[match]; data[match] = data[pair] - product; data[pair] += product; } factor = multiplier * factor + factor; } } } /// unittest { Complexf[4] data = [ Complexf(1.0f, 0.0f), Complexf(2.0f, 0.0f), Complexf(3.0f, 0.0f), Complexf(4.0f, 0.0f) ]; fastFourierTransform(data, true); assert(data[0].toString == "10 + 0i"); assert(data[1].toString == "-2 + 2i"); assert(data[2].toString == "-2 + 0i"); assert(data[3].toString == "-2 + -2i"); } ================================================ FILE: dlib/math/hof.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Functions that return other functions * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.hof; /** * Functional composition. * Description: * Returns a function that applies function f to the return value of function g */ T delegate(S) compose(T, U, S)(T function(U) f, U function(S) g) { return (S s) => f(g(s)); } /** Y combinator Description: We're all familiar with the idea of a function as something that takes some input value and returns some output value. Say, the function for squaring numbers: --- f(x) = x*x; --- The fixed points of a function are any input values for which f(x) is equal to x. So, the fixed points of f(x) = x*x are 0 and 1. Now, we have things called higher-order functions. These are functions that take another function as input, or return a function as output, or both. The fixed point of a higher-order function f is another function p such that f(p) = p. It may be more helpful to think in terms of functions actually being executed. The previous statement is equivalent to the statement that f(p)(x) = p(x) for all values of x. Y (the Y combinator) is a special function that returns the fixed points of higher-order functions, that is to say: --- f(Y(f)) = Y(f) --- Y combinator is commonly use to allow anonymous recursion without assuming your host language supports it. */ auto Y(R, P...) (R delegate(P) delegate(R delegate(P)) lambda) { struct RFunc {R delegate(P) delegate(RFunc) f;} auto r = RFunc((RFunc w) => lambda((P x) => w.f(w)(x))); return r.f(r); } /// unittest { import std.algorithm; import std.range; auto factorial = Y((int delegate(int) self) => (int n) => 0 == n ? 1 : n * self(n - 1)); auto ackermann = Y((ulong delegate(ulong, ulong) self) => (ulong m, ulong n) => (!m && n)? (n+1) : (m && !n)? self(m-1, 1) : self(m-1, self(m, n-1))); auto qsort = Y((int[] delegate(int[]) self) => (int[] arr) => arr.length? self(arr.filter!(a => a < arr[0]).array) ~ arr[0] ~ self(arr.filter!(a => a > arr[0]).array) : []); assert(factorial(6) == 720); assert(ackermann(3, 5) == 253); assert(qsort([8, 5, 10, 2, 16, 9, 1, 100, 3]) == [1, 2, 3, 5, 8, 9, 10, 16, 100]); } ================================================ FILE: dlib/math/interpolation/bezier.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Bézier interpolation functions * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.interpolation.bezier; import dlib.math.vector; /** * Computes quadratic Bézier curve */ T bezierQuadratic(T)(T A, T B, T C, T t) { T s = cast(T)1.0 - t; return s * s * A + 2.0 * s * B + t * t * C; } /** * Computes cubic Bézier curve */ T bezierCubic(T) (T A, T B, T C, T D, T t) { T s = cast(T)1.0 - t; T s2 = s * s; T s3 = s2 * s; return s3 * A + 3.0 * t * s2 * B + 3.0 * t * t * s * C + t * t * t * D; } /// ditto alias bezier = bezierCubic; /** * Computes cubic Bézier curve tangent */ T bezierCubicTangent(T)(T a, T b, T c, T d, T t) { T c1 = (d - (3.0 * c) + (3.0 * b) - a); T c2 = ((3.0 * c) - (6.0 * b) + (3.0 * a)); T c3 = ((3.0 * b) - (3.0 * a)); return ((3.0 * c1 * t * t) + (2.0 * c2 * t) + c3); } /// ditto alias bezierTangent = bezierCubicTangent; /** * Computes cubic Bézier curve in 2D space */ Vector!(T,2) bezierVector2(T)( Vector!(T,2) a, Vector!(T,2) b, Vector!(T,2) c, Vector!(T,2) d, T t) { return Vector!(T,2) ( bezier(a.x, b.x, c.x, d.x, t), bezier(a.y, b.y, c.y, d.y, t) ); } /** * Computes cubic Bézier curve in 3D space */ Vector!(T,3) bezierVector3(T)( Vector!(T,3) a, Vector!(T,3) b, Vector!(T,3) c, Vector!(T,3) d, T t) { return Vector!(T,3) ( bezier(a.x, b.x, c.x, d.x, t), bezier(a.y, b.y, c.y, d.y, t), bezier(a.z, b.z, c.z, d.z, t) ); } /** * Computes cubic Bézier curve tangent in 2D space */ Vector!(T,2) bezierTangentVector2(T)( Vector!(T,2) a, Vector!(T,2) b, Vector!(T,2) c, Vector!(T,2) d, T t) { return Vector!(T,2) ( bezierTangent(a.x, b.x, c.x, d.x, t), bezierTangent(a.y, b.y, c.y, d.y, t) ); } /** * Computes cubic Bézier curve tangent in 3D space */ Vector!(T,3) bezierTangentVector3(T)( Vector!(T,3) a, Vector!(T,3) b, Vector!(T,3) c, Vector!(T,3) d, T t) { return Vector!(T,3) ( bezierTangent(a.x, b.x, c.x, d.x, t), bezierTangent(a.y, b.y, c.y, d.y, t), bezierTangent(a.z, b.z, c.z, d.z, t) ); } ================================================ FILE: dlib/math/interpolation/catmullrom.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Catmull-Rom interpolation functions * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.interpolation.catmullrom; import std.math; /** * Catmull-Rom curve */ T interpCatmullRom(T) (T p0, T p1, T p2, T p3, float t) { return 0.5 * ((2 * p1) + (-p0 + p2) * t + (2 * p0 - 5 * p1 + 4 * p2 - p3) * t^^2 + (-p0 + 3 * p1 - 3 * p2 + p3) * t^^3); } /** * Catmull-Rom curve derivative */ T interpCatmullRomDerivative(T) (T p0, T p1, T p2, T p3, float t) { return 0.5 * ((2 * p1) + (-p0 + p2) + 2 * (2 * p0 - 5 * p1 + 4 * p2 - p3) * t + 3 * (-p0 + 3 * p1 - 3 * p2 + p3) * t^^2); } ================================================ FILE: dlib/math/interpolation/easing.d ================================================ /* Copyright (c) 2019-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Easing functions for animation. * * Copyright: Timur Gafarov 2019-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.interpolation.easing; import std.math; /// Quadratic ease in T easeInQuad(T)(T t) { return t * t; } /// Quadratic ease out T easeOutQuad(T)(T t) { return -t * (t - 2); } /// Quadratic ease in-out T easeInOutQuad(T)(T t) { t /= 0.5; if (t < 1.0) return 0.5 * t * t; t--; return -0.5 * (t * (t - 2.0) - 1.0); } /// Back ease in T easeInBack(T)(T t) { enum T s = 1.70158; return t * t * ((s + 1) * t - s); } /// Back ease out T easeOutBack(T)(T t) { enum T s = 1.70158; t = t - 1.0; return (t * t * ((s + 1) * t + s) + 1.0); } /// Back ease in-out T easeInOutBack(T)(T t) { float s = 1.70158; t /= 0.5; if (t < 1.0) { s *= 1.525; return 0.5 * (t * t * ((s + 1.0) * t - s)); } t -= 2.0; s *= 1.525; return 0.5 * (t * t * ((s + 1.0) * t + s) + 2.0); } /// Bounce ease out T easeOutBounce(T)(T t) { if (t < (1.0 / 2.75)) { return (7.5625 * t * t); } else if (t < (2.0 / 2.75)) { t -= (1.5 / 2.75); return (7.5625 * (t) * t + 0.75); } else if (t < (2.5 / 2.75)) { t -= (2.25 / 2.75); return (7.5625 * (t) * t + 0.9375); } else { t -= (2.625 / 2.75); return (7.5625 * (t) * t + 0.984375); } } /// Elastic ease out T easeOutElastic(T)(T t) { T c4 = (2 * PI) / 3; return t == 0? 0 : t == 1 ? 1 : pow(2, -10 * t) * sin((t * 10 - 0.75) * c4) + 1; } ================================================ FILE: dlib/math/interpolation/hermite.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Hermite interpolation functions * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.interpolation.hermite; /** * Hermite curve */ T interpHermite(T) (T x, T tx, T y, T ty, float t) { float t2 = t * t; float t3 = t2 * t; float h1 = t3 * 2.0 - t2 * 3.0 + 1.0; float h2 = t2 * 3.0 - t3 * 2.0; float h3 = t3 - t2 * 2.0 + t; float h4 = t3 - t2; return x * h1 + tx * h3 + y * h2 + ty * h4; } /** * Hermite curve derivative */ T interpHermiteDerivative(T) (T x, T tx, T y, T ty, float t) { float t2 = t * t; float h1 = t2 * 6.0 - t * 6.0; float h2 = t * 6.0 - t2 * 6.0; float h3 = t2 * 3.0 - t * 4.0 + 1.0; float h4 = t2 * 3.0 - t * 2.0; return x * h1 + tx * h3 + y * h2 + ty * h4; } ================================================ FILE: dlib/math/interpolation/linear.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Linear interpolation * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.interpolation.linear; import std.math; /// Linear interpolation T interpLinear(T) (T a, T b, float t) { return a + (b - a) * t; } /// ditto alias lerp = interpLinear; /// unittest { assert(lerp(0.0f, 2.0f, 0.5f) == 1.0f); } ================================================ FILE: dlib/math/interpolation/nearest.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Nearest-neighbour interpolation * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.interpolation.nearest; import std.math; /// Nearest-neighbour interpolation T interpNearest(T) (T x, T y, float t) { if (t < 0.5f) return x; else return y; } /// unittest { assert(interpNearest(0.0f, 2.0f, 0.3f) == 0.0f); } ================================================ FILE: dlib/math/interpolation/package.d ================================================ /* Copyright (c) 2020-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Interpolation functions * * Copyright: Timur Gafarov 2020-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.interpolation; public { import dlib.math.interpolation.bezier; import dlib.math.interpolation.catmullrom; import dlib.math.interpolation.easing; import dlib.math.interpolation.hermite; import dlib.math.interpolation.linear; import dlib.math.interpolation.nearest; import dlib.math.interpolation.smoothstep; } ================================================ FILE: dlib/math/interpolation/smoothstep.d ================================================ /* Copyright (c) 2018-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Sigmoid-like functions for clamping and non-linear interpolation of values in [0, 1] range. * * Copyright: Timur Gafarov 2018-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.interpolation.smoothstep; import std.math; import dlib.math.utils; /** * Hermite polynomial, analogous to GLSL smoothstep. * e0 and e1 define lower and upper edges of Hermite function. */ T hermiteSmoothstep(T)(T x, float e0, float e1) { T t = clamp((x - e0) / (e1 - e0), 0.0, 1.0); return t * t * (3.0 - 2.0 * t); } /** * Rational sigmoid that becomes linear at k=0 and discrete at k=1. * Allows varying between linear and nearest-neighbour interpolation. */ T rationalSmoothstep(T)(T x, float k) { T s = (x + x * k - k * 0.5 - 0.5) / (abs(x * k * 4.0 - k * 2.0) - k + 1.0) + 0.5; return clamp(s, 0.0, 1.0); } ================================================ FILE: dlib/math/linsolve.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Linear equation system solvers * * Description: * A system is given in matrix form: * --- * Ax = b * --- * For example: * --- * x + 3y - 2z = 5 * 3x + 5y + 6z = 7 * 2x + 4y + 3z = 8 * --- * For this system, A (coefficient matrix) will be * --- * [1, 3, -2] * [3, 5, 6] * [2, 4, 3] * --- * And b (right side vector) will be * --- * [5, 7, 8] * --- * x is a vector of unknowns: * --- * [x, y, z] * --- * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.linsolve; import std.math; import dlib.math.matrix; import dlib.math.vector; import dlib.math.decomposition; // TODO: use arrays instead of Matrix structs to support big systems stored in heap /// Solve Ax = b using LUP decomposition void solve(T, size_t N)( Matrix!(T,N) a, ref Vector!(T,N) x, Vector!(T,N) b) { Matrix!(T,N) L, U, P; decomposeLUP(a, L, U, P); solveLU(L, U, x, b * P); } /// unittest { bool isConsiderZeroTolerant(T) (T f) nothrow { return (abs(f) < 0.0001f); } Matrix3f a = matrixf( 1, 3, -2, 3, 5, 6, 2, 4, 3 ); Vector3f b = Vector3f(5, 7, 8); Vector3f x = Vector3f(0, 0, 0); solve(a, x, b); assert(isConsiderZeroTolerant(-15 - x[0])); assert(isConsiderZeroTolerant(8 - x[1])); assert(isConsiderZeroTolerant(2 - x[2])); } /// Solve LUx = b void solveLU(T, size_t N)( Matrix!(T,N) L, Matrix!(T,N) U, ref Vector!(T,N) x, Vector!(T,N) b) { int i = 0; int j = 0; Vector!(T,N) y; // Forward solve Ly = b for (i = 0; i < N; i++) { y[i] = b[i]; for (j = 0; j < i; j++) y[i] -= L[i, j] * y[j]; y[i] /= L[i, i]; } // Backward solve Ux = y for (i = N - 1; i >= 0; i--) { x[i] = y[i]; for (j = i + 1; j < N; j++) x[i] -= U[i, j] * x[j]; x[i] /= U[i, i]; } } ================================================ FILE: dlib/math/matrix.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov, Martin Cejp Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Square matrices with static memory allocation * * Copyright: Timur Gafarov, Martin Cejp 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov, Martin Cejp */ module dlib.math.matrix; import std.math; import std.range; import std.format; import std.conv; import std.string; import dlib.math.vector; import dlib.math.utils; import dlib.math.decomposition; import dlib.math.linsolve; /** * Square (NxN) matrix. * * Implementation notes: * * - The storage order is column-major. * * - Affine vector of 4x4 matrix is in the 4th column (as in OpenGL). * * - Elements are stored in a fixed manner, so it is impossible to change * matrix size once it's created. * * - Actual data is allocated as a static array, so no references, no GC touching. * When you pass a Matrix by value, it will be safely copied. * * - This implementation is not perfect (as for now) for dealing with really * big matrices, but ideal for smaller ones, e.g. those which are meant to be * manipulated in real-time (in game engines, rendering pipelines etc). */ struct Matrix(T, size_t N) { /** * Compare two matrices. * * Params: * that = The matrix to compare with. * * Returns: $(D_KEYWORD true) if dimensions are equal, $(D_KEYWORD false) otherwise. */ bool opEquals(Matrix!(T, N) that) const { return arrayof == that.arrayof; } /** * Return zero matrix */ static zero() do { Matrix!(T,N) res; foreach (ref v; res.arrayof) v = 0; return res; } /** * Return identity matrix */ static identity() do { Matrix!(T,N) res; res.setIdentity(); return res; } /** * Set to identity */ void setIdentity() do { foreach(y; 0..N) foreach(x; 0..N) { if (y == x) arrayof[y * N + x] = 1; else arrayof[y * N + x] = 0; } } /** * Create matrix from array. * This is a convenient way to deal with arrays of "classic" layout: * the storage order in an array should be row-major */ this(F)(F[] arr) in { assert (arr.length == N * N, "Matrix!(T,N): wrong array length in constructor"); } do { foreach (i, ref v; arrayof) { auto i2 = i / N + N * (i - N * (i / N)); v = arr[i2]; } } /** * T = Matrix[i, j] */ T opIndex(in size_t i, in size_t j) const do { return arrayof[j * N + i]; } /** * Matrix[i, j] = T */ T opIndexAssign(in T t, in size_t i, in size_t j) do { return (arrayof[j * N + i] = t); } /** * T = Matrix[index] * Indices start with 0 */ T opIndex(in size_t index) const in { assert ((0 <= index) && (index < N * N), "Matrix.opIndex(int index): array index out of bounds"); } do { return arrayof[index]; } /** * Matrix[index] = T * Indices start with 0 */ T opIndexAssign(in T t, in size_t index) in { assert ((0 <= index) && (index < N * N), "Matrix.opIndexAssign(T t, int index): array index out of bounds"); } do { return (arrayof[index] = t); } /** * Matrix4x4!(T)[index1..index2] = T */ T[] opSliceAssign(in T t, in size_t index1, in size_t index2) in { assert ((0 <= index1) && (index1 < N * N), "Matrix.opSliceAssign(T t, int index1, int index2): index1 out of bounds"); assert ((0 <= index2) && (index2 < N * N), "Matrix.opSliceAssign(T t, int index1, int index2): index2 out of bounds"); } do { return (arrayof[index1..index2] = t); } /** * Matrix[] = T */ T[] opSliceAssign(in T t) do { return (arrayof[] = t); } /** * Matrix + Matrix */ Matrix!(T,N) opBinary(string op)(Matrix!(T,N) mat) const if (op == "+") do { auto res = Matrix!(T,N)(); foreach (i; 0..N) foreach (j; 0..N) { res[i, j] = this[i, j] + mat[i, j]; } return res; } /** * Matrix - Matrix */ Matrix!(T,N) opBinary(string op)(Matrix!(T,N) mat) const if (op == "-") do { auto res = Matrix!(T,N)(); foreach (i; 0..N) foreach (j; 0..N) { res[i, j] = this[i, j] - mat[i, j]; } return res; } /** * Matrix * Matrix */ Matrix!(T,N) opBinary(string op)(Matrix!(T,N) mat) const if (op == "*") do { static if (N == 2) { Matrix!(T,N) res; res.a11 = (a11 * mat.a11) + (a12 * mat.a21); res.a12 = (a11 * mat.a12) + (a12 * mat.a22); res.a21 = (a21 * mat.a11) + (a22 * mat.a21); res.a22 = (a21 * mat.a12) + (a22 * mat.a22); return res; } else static if (N == 3) { Matrix!(T,N) res; res.a11 = (a11 * mat.a11) + (a12 * mat.a21) + (a13 * mat.a31); res.a12 = (a11 * mat.a12) + (a12 * mat.a22) + (a13 * mat.a32); res.a13 = (a11 * mat.a13) + (a12 * mat.a23) + (a13 * mat.a33); res.a21 = (a21 * mat.a11) + (a22 * mat.a21) + (a23 * mat.a31); res.a22 = (a21 * mat.a12) + (a22 * mat.a22) + (a23 * mat.a32); res.a23 = (a21 * mat.a13) + (a22 * mat.a23) + (a23 * mat.a33); res.a31 = (a31 * mat.a11) + (a32 * mat.a21) + (a33 * mat.a31); res.a32 = (a31 * mat.a12) + (a32 * mat.a22) + (a33 * mat.a32); res.a33 = (a31 * mat.a13) + (a32 * mat.a23) + (a33 * mat.a33); return res; } else static if (N == 4) { Matrix!(T,N) res; res.a11 = (a11 * mat.a11) + (a12 * mat.a21) + (a13 * mat.a31) + (a14 * mat.a41); res.a12 = (a11 * mat.a12) + (a12 * mat.a22) + (a13 * mat.a32) + (a14 * mat.a42); res.a13 = (a11 * mat.a13) + (a12 * mat.a23) + (a13 * mat.a33) + (a14 * mat.a43); res.a14 = (a11 * mat.a14) + (a12 * mat.a24) + (a13 * mat.a34) + (a14 * mat.a44); res.a21 = (a21 * mat.a11) + (a22 * mat.a21) + (a23 * mat.a31) + (a24 * mat.a41); res.a22 = (a21 * mat.a12) + (a22 * mat.a22) + (a23 * mat.a32) + (a24 * mat.a42); res.a23 = (a21 * mat.a13) + (a22 * mat.a23) + (a23 * mat.a33) + (a24 * mat.a43); res.a24 = (a21 * mat.a14) + (a22 * mat.a24) + (a23 * mat.a34) + (a24 * mat.a44); res.a31 = (a31 * mat.a11) + (a32 * mat.a21) + (a33 * mat.a31) + (a34 * mat.a41); res.a32 = (a31 * mat.a12) + (a32 * mat.a22) + (a33 * mat.a32) + (a34 * mat.a42); res.a33 = (a31 * mat.a13) + (a32 * mat.a23) + (a33 * mat.a33) + (a34 * mat.a43); res.a34 = (a31 * mat.a14) + (a32 * mat.a24) + (a33 * mat.a34) + (a34 * mat.a44); res.a41 = (a41 * mat.a11) + (a42 * mat.a21) + (a43 * mat.a31) + (a44 * mat.a41); res.a42 = (a41 * mat.a12) + (a42 * mat.a22) + (a43 * mat.a32) + (a44 * mat.a42); res.a43 = (a41 * mat.a13) + (a42 * mat.a23) + (a43 * mat.a33) + (a44 * mat.a43); res.a44 = (a41 * mat.a14) + (a42 * mat.a24) + (a43 * mat.a34) + (a44 * mat.a44); return res; } else { auto res = Matrix!(T,N)(); foreach (i; 0..N) foreach (j; 0..N) { T sumProduct = 0; foreach (k; 0..N) sumProduct += this[i, k] * mat[k, j]; res[i, j] = sumProduct; } return res; } } /** * Matrix += Matrix */ Matrix!(T,N) opOpAssign(string op)(Matrix!(T,N) mat) if (op == "+") do { this = this + mat; return this; } /** * Matrix -= Matrix */ Matrix!(T,N) opOpAssign(string op)(Matrix!(T,N) mat) if (op == "-") do { this = this - mat; return this; } /** * Matrix *= Matrix */ Matrix!(T,N) opOpAssign(string op)(Matrix!(T,N) mat) if (op == "*") do { this = this * mat; return this; } /** * Matrix * T */ Matrix!(T,N) opBinary(string op)(T k) const if (op == "*") do { auto res = Matrix!(T,N)(); foreach(i, v; arrayof) res.arrayof[i] = v * k; return res; } /** * Matrix *= T */ Matrix!(T,N) opOpAssign(string op)(T k) if (op == "*") do { foreach(ref v; arrayof) v *= k; return this; } /** * Multiply column vector by the matrix */ static if (N == 2) { Vector!(T,2) opBinaryRight(string op) (Vector!(T,2) v) const if (op == "*") do { return Vector!(T,2) ( (v.x * a11) + (v.y * a12), (v.x * a21) + (v.y * a22) ); } } else static if (N == 3) { Vector!(T,3) opBinaryRight(string op) (Vector!(T,3) v) const if (op == "*") do { return Vector!(T,3) ( (v.x * a11) + (v.y * a12) + (v.z * a13), (v.x * a21) + (v.y * a22) + (v.z * a23), (v.x * a31) + (v.y * a32) + (v.z * a33) ); } } else { Vector!(T,N) opBinaryRight(string op) (Vector!(T,N) v) const if (op == "*") do { Vector!(T,N) res; foreach(x; 0..N) { T n = 0; foreach(y; 0..N) n += v.arrayof[y] * arrayof[y * N + x]; res.arrayof[x] = n; } return res; } } /** * Multiply column 3D vector by the affine 4x4 matrix */ static if (N == 4) { Vector!(T,3) opBinaryRight(string op) (Vector!(T,3) v) const if (op == "*") do { return Vector!(T,3) ( (v.x * a11) + (v.y * a12) + (v.z * a13) + a14, (v.x * a21) + (v.y * a22) + (v.z * a23) + a24, (v.x * a31) + (v.y * a32) + (v.z * a33) + a34 ); } } static if (N == 3 || N == 4) { /** * Rotate a vector by the 3x3 upper-left portion of the matrix */ Vector!(T,3) rotate(Vector!(T,3) v) const do { return Vector!(T,3) ( (v.x * a11) + (v.y * a12) + (v.z * a13), (v.x * a21) + (v.y * a22) + (v.z * a23), (v.x * a31) + (v.y * a32) + (v.z * a33) ); } /** * Rotate a vector by the inverse 3x3 upper-left portion of the matrix */ Vector!(T,3) invRotate(Vector!(T,3) v) const do { return Vector!(T,3) ( (v.x * a11) + (v.y * a21) + (v.z * a31), (v.x * a12) + (v.y * a22) + (v.z * a32), (v.x * a13) + (v.y * a23) + (v.z * a33) ); } } static if (N == 4 || N == 3) { T determinant3x3() const do { return a11 * (a33 * a22 - a32 * a23) - a21 * (a33 * a12 - a32 * a13) + a31 * (a23 * a12 - a22 * a13); } } static if (N == 1) { /** * Determinant (of upper-left 3x3 portion for 4x4 matrices) */ T determinant() const do { return a11; } } else static if (N == 2) { T determinant() const do { return a11 * a22 - a12 * a21; } } else static if (N == 3) { alias determinant = determinant3x3; } else { // Determinant of a given upper-left portion T determinant(size_t n = N) const do { T d = 0; if (n == 1) d = this[0,0]; else if (n == 2) d = this[0,0] * this[1,1] - this[1,0] * this[0,1]; else { auto submat = Matrix!(T,N)(); for (uint c = 0; c < n; c++) { uint subi = 0; for (uint i = 1; i < n; i++) { uint subj = 0; for (uint j = 0; j < n; j++) { if (j == c) continue; submat[subi, subj] = this[i, j]; subj++; } subi++; } d += pow(-1, c + 2.0) * this[0, c] * submat.determinant(n-1); } } return d; } } alias det = determinant; /** * Return true if matrix is singular */ bool isSingular() @property do { return (determinant == 0); } alias singular = isSingular; /** * Check if matrix represents affine transformation */ static if (N == 4) { bool isAffine() const @property do { return (a41 == 0.0 && a42 == 0.0 && a43 == 0.0 && a44 == 1.0); } alias affine = isAffine; } /** * Transpose */ void transpose() do { this = transposed; } /** * Return the transposed matrix */ Matrix!(T,N) transposed() @property do { Matrix!(T,N) res; foreach(y; 0..N) foreach(x; 0..N) res.arrayof[y * N + x] = arrayof[x * N + y]; return res; } /** * Invert */ void invert() do { this = inverse; } /** * Inverse of a matrix */ static if (N == 1) { Matrix!(T,N) inverse() const @property do { Matrix!(T,N) res; res.a11 = 1.0 / a11; return res; } } else static if (N == 2) { Matrix!(T,N) inverse() const @property do { Matrix!(T,N) res; T invd = 1.0 / (a11 * a22 - a12 * a21); res.a11 = a22 * invd; res.a12 = -a12 * invd; res.a22 = a11 * invd; res.a21 = -a21 * invd; return res; } } else static if (N == 3) { Matrix!(T,N) inverse() const @property do { T d = determinant; T oneOverDet = 1.0 / d; Matrix!(T,N) res; res.a11 = (a33 * a22 - a32 * a23) * oneOverDet; res.a12 = -(a33 * a12 - a32 * a13) * oneOverDet; res.a13 = (a23 * a12 - a22 * a13) * oneOverDet; res.a21 = -(a33 * a21 - a31 * a23) * oneOverDet; res.a22 = (a33 * a11 - a31 * a13) * oneOverDet; res.a23 = -(a23 * a11 - a21 * a13) * oneOverDet; res.a31 = (a32 * a21 - a31 * a22) * oneOverDet; res.a32 = -(a32 * a11 - a31 * a12) * oneOverDet; res.a33 = (a22 * a11 - a21 * a12) * oneOverDet; return res; } } else { Matrix!(T,N) inverse() const @property do { Matrix!(T,N) res; // Inversion via LU decomposition enum inv = q{{ Matrix!(T,N) l, u, p; decomposeLUP(this, l, u, p); foreach(j; 0..N) { Vector!(T,N) b = p.getColumn(j); Vector!(T,N) x; solveLU(l, u, x, b); res.setColumn(j, x); } }}; // Inverse of a 4x4 affine matrix is a special case enum affineInv = q{{ auto m3inv = matrix4x4to3x3(this).inverse; res = matrix3x3to4x4(m3inv); Vector!(T,3) t = -(getColumn(3).xyz * m3inv); res.setColumn(3, Vector!(T,4)(t.x, t.y, t.z, 1.0f)); }}; static if (N == 4) { if (affine) mixin(affineInv); else mixin(inv); } else mixin(inv); return res; } } /** * Adjugate and cofactor matrices */ static if (N == 1) { Matrix!(T,N) adjugate() @property do { Matrix!(T,N) res; res.arrayof[0] = 1; return res; } Matrix!(T,N) cofactor() @property { Matrix!(T,N) res; res.arrayof[0] = 1; return res; } } else static if (N == 2) { Matrix!(T,N) adjugate() @property do { Matrix!(T,N) res; res.arrayof[0] = arrayof[3]; res.arrayof[1] = -arrayof[1]; res.arrayof[2] = -arrayof[2]; res.arrayof[3] = arrayof[0]; return res; } Matrix!(T,N) cofactor() @property { Matrix!(T,N) res; res.arrayof[0] = arrayof[3]; res.arrayof[1] = -arrayof[2]; res.arrayof[2] = -arrayof[1]; res.arrayof[3] = arrayof[0]; return res; } } else { Matrix!(T,N) adjugate() @property do { return cofactor.transposed; } Matrix!(T,N) cofactor() @property do { Matrix!(T,N) res; foreach(y; 0..N) foreach(x; 0..N) { auto submat = Matrix!(T,N-1)(); uint suby = 0; foreach(yy; 0..N) if (yy != y) { uint subx = 0; foreach(xx; 0..N) if (xx != x) { submat[subx, suby] = this[xx, yy]; subx++; } suby++; } res[x, y] = submat.determinant * (((x + y) % 2)? -1:1); } return res; } } /** * Negative matrix */ Matrix!(T,N) negative() @property do { return this * -1; } /** * Convert to string */ string toString() @property do { return matrixToStr(this); } /** * Symbolic element access */ private static string elements(string letter) @property do { string res; foreach (x; 0..N) foreach (y; 0..N) { res ~= "T " ~ letter ~ to!string(y+1) ~ to!string(x+1) ~ ";"; } return res; } /** * Row/column manipulations */ Vector!(T,N) getRow(size_t i) { Vector!(T,N) res; for (size_t j = 0; j < N; j++) res.arrayof[j] = this[i, j]; return res; } void setRow(size_t i, Vector!(T,N) v) { for (size_t j = 0; j < N; j++) this[i, j] = v.arrayof[j]; } Vector!(T,N) getColumn(size_t j) const { Vector!(T,N) res; for (size_t i = 0; i < N; i++) res.arrayof[i] = this[i, j]; return res; } void setColumn(size_t j, Vector!(T,N) v) { for (size_t i = 0; i < N; i++) this[i, j] = v.arrayof[i]; } void swapRows(size_t r1, size_t r2) { for (size_t j = 0; j < N; j++) { T t = this[r1, j]; this[r1, j] = this[r2, j]; this[r2, j] = t; } } void swapColumns(size_t c1, size_t c2) { for (size_t i = 0; i < N; i++) { T t = this[i, c1]; this[i, c1] = this[i, c2]; this[i, c2] = t; } } auto flatten() { return transposed.arrayof; } /** * Matrix elements */ union { /** * This auto-generated structure provides symbolic access * to matrix elements, like in standard mathematic notation: * --- * a11 a12 a13 a14 .. a1N * a21 a22 a23 a24 .. a2N * a31 a32 a33 a34 .. a3N * a41 a42 a43 a44 .. a4N * : : : : . * aN1 aN2 aN3 aN4 ' aNN * --- */ struct { mixin(elements("a")); } /** * Linear array representing elements column by column */ T[N * N] arrayof; } } /// unittest { auto m1 = matrixf( 1, 2, 0, 6, 4, 6, 3, 1, 2, 7, 8, 2, 0, 5, 2, 1 ); auto m2 = matrixf( 0, 3, 7, 1, 1, 0, 2, 5, 1, 9, 2, 6, 5, 2, 0, 0 ); assert(m1 * m2 == matrixf( 32, 15, 11, 11, 14, 41, 46, 52, 25, 82, 44, 85, 12, 20, 14, 37) ); auto m3 = Matrix4f.identity; assert(m3 == matrixf( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) ); m3[12] = 1; m3.a24 = 2; m3.a34 = 3; m3[1..4] = 0; assert(m3[12] == 1); assert(m1.determinant3x3 == -25); assert(m1.determinant == 567); assert(m1.singular == false); assert(m1.affine == false); assert(m1.transposed == matrixf( 1, 4, 2, 0, 2, 6, 7, 5, 0, 3, 8, 2, 6, 1, 2, 1) ); auto m4 = matrixf( 0, 3, 2, 1, 0, 8, 0, 1, 0 ); assert(m4.inverse == matrixf( -4, 1, 12, -0, 0, 1, 0.5, 0, -1.5) ); assert(m1.adjugate == matrixf( 7, 148, -16, -158, -14, 28, -49, 154, -14, -53, 113, -89, 98, -34, 19, -25) ); assert(m1.cofactor == matrixf( 7, -14, -14, 98, 148, 28, -53, -34, -16, -49, 113, 19, -158, 154, -89, -25) ); m1.transpose(); assert(m1 == matrixf( 1, 4, 2, 0, 2, 6, 7, 5, 0, 3, 8, 2, 6, 1, 2, 1) ); Matrix2f m5; m5[] = 1.0f; m5 += matrixf( 2, 2, 2, 2 ); m5 *= m5; assert(m5 == matrixf( 18, 18, 18, 18) ); m5 = m5 - m5; assert(m5 == Matrix2f.zero); Matrix2f m6 = matrixf( 2, 2, 2, 2 ); m6 = m6 * m6; m6 = m6 * 2; m6 *= 2; assert(m6 == matrixf( 32, 32, 32, 32) ); assert(m6.determinant == 0); Matrix3f m7 = matrixf( 3, 3, 3, 3, 3, 3, 3, 3, 3 ); m7 = m7 * m7; assert(m7 == matrixf( 27, 27, 27, 27, 27, 27, 27, 27, 27) ); Matrix2f m8 = matrixf( 1, 0, 0, 1 ); m8.invert(); assert(m8 == matrixf( 1, 0, 0, 1) ); assert(m8.negative == matrixf( -1, 0, 0, -1) ); auto m9 = matrixf( 1, 0, 0, 2, 0, 1, 0, 3, 0, 0, 1, 4, 0, 0, 0, 1 ); assert(m9.affine == true); assert(m9.inverse == matrixf( 1, 0, 0, -2, 0, 1, 0, -3, 0, 0, 1, -4, 0, 0, 0, 1) ); bool isAlmostZero3(Vector3f v) { float e = 0.002f; return abs(v.x) < e && abs(v.y) < e && abs(v.z) < e; } Vector3f v1 = Vector3f(1, 0, 0); v1 = v1 * matrixf( 1, 0, 0, 2, 0, 1, 0, 3, 0, 0, 1, 4, 0, 0, 0, 1 ); assert(v1 == Vector3f(3, 3, 4)); Vector3f v2 = Vector3f(0, 1, 0); const float a1 = PI * 0.5f; v2 = matrixf( 1, 0, 0, 0, 0, cos(a1), -sin(a1), 0, 0, sin(a1), cos(a1), 0, 0, 0, 0, 1 ).rotate(v2); assert(isAlmostZero3(v2 - Vector3f(0, 0, 1))); Vector3f v3 = Vector3f(0, 1, 0); v3 = matrixf( 1, 0, 0, 0, 0, cos(a1), -sin(a1), 0, 0, sin(a1), cos(a1), 0, 0, 0, 0, 1 ).invRotate(v3); assert(isAlmostZero3(v3 - Vector3f(0, 0, -1))); auto m10 = matrixf( 1, 2, 3, 3, 2, 1, 2, 3, 1 ); Vector3f r0 = m10.getRow(0); assert(isAlmostZero3(r0 - Vector3f(1, 2, 3))); Vector3f c0 = m10.getColumn(0); assert(isAlmostZero3(c0 - Vector3f(1, 3, 2))); m10.setRow(2, Vector3f(1, 1, 1)); Vector3f r2 = m10.getRow(2); assert(isAlmostZero3(r2 - Vector3f(1, 1, 1))); m10.setColumn(2, Vector3f(1, 1, 1)); Vector3f c2 = m10.getColumn(2); assert(isAlmostZero3(c2 - Vector3f(1, 1, 1))); m10.swapRows(0, 1); Vector3f r1 = m10.getRow(1); assert(isAlmostZero3(r1 - Vector3f(1, 2, 1))); m10.swapColumns(1, 2); Vector3f c1 = m10.getColumn(1); assert(isAlmostZero3(c1 - Vector3f(1, 1, 1))); Matrix2f m11 = matrixf( 2, 1, 2, 1 ); assert(m11.adjugate == matrixf( 1, -1, -2, 2) ); assert(m11.cofactor == matrixf( 1, -2, -1, 2) ); assert(m11.flatten == [2, 1, 2, 1]); assert(m11.elements("a") == "T a11;T a21;T a12;T a22;"); Matrix!(float, 1) m12 = matrixf(1); assert(m12.determinant == 1); assert(m12.inverse == matrixf(1)); assert(m12.adjugate == matrixf(1)); assert(m12.cofactor == matrixf(1)); } /* * Predefined matrix type aliases */ /// Alias for single precision 2x2 Matrix alias Matrix!(float, 2) Matrix2x2f, Matrix2f; /// Alias for single precision 3x3 Matrix alias Matrix!(float, 3) Matrix3x3f, Matrix3f; /// Alias for single precision 4x4 Matrix alias Matrix!(float, 4) Matrix4x4f, Matrix4f; /// Alias for double precision 2x2 Matrix alias Matrix!(double, 2) Matrix2x2d, Matrix2d; /// Alias for double precision 3x3 Matrix alias Matrix!(double, 3) Matrix3x3d, Matrix3d; /// Alias for double precision 4x4 Matrix alias Matrix!(double, 4) Matrix4x4d, Matrix4d; /** * Short aliases */ alias mat2 = Matrix2x2f; alias mat3 = Matrix3x3f; alias mat4 = Matrix4x4f; /** * Matrix factory function */ auto matrixf(A...)(A arr) { static assert(isPerfectSquare(arr.length), "matrixf(A): input array length is not perfect square integer"); return Matrix!(float, cast(size_t)sqrt(cast(float)arr.length))([arr]); } /** * Converts 3x3 matrix to 4x4 matrix. * 4x4 matrix defaults to identity */ Matrix!(T,4) matrix3x3to4x4(T) (Matrix!(T,3) m) { auto res = Matrix!(T,4).identity; res.a11 = m.a11; res.a12 = m.a12; res.a13 = m.a13; res.a21 = m.a21; res.a22 = m.a22; res.a23 = m.a23; res.a31 = m.a31; res.a32 = m.a32; res.a33 = m.a33; return res; } /// unittest { Matrix3f m1 = matrixf( 1, 0, 0, 0, 1, 0, 0, 0, 1 ); Matrix4f m2 = matrix3x3to4x4(m1); assert(m2 == matrixf( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) ); } /** * Converts 4x4 matrix to 3x3 matrix. * 3x3 matrix defaults to identity */ Matrix!(T,3) matrix4x4to3x3(T) (Matrix!(T,4) m) { auto res = Matrix!(T,3).identity; res.a11 = m.a11; res.a12 = m.a12; res.a13 = m.a13; res.a21 = m.a21; res.a22 = m.a22; res.a23 = m.a23; res.a31 = m.a31; res.a32 = m.a32; res.a33 = m.a33; return res; } /// unittest { Matrix4f m1 = matrixf( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ); Matrix3f m2 = matrix4x4to3x3(m1); assert(m2 == matrixf( 1, 0, 0, 0, 1, 0, 0, 0, 1) ); } /** * Formatted matrix printer */ string matrixToStr(T, size_t N)(Matrix!(T, N) m) { uint width = 8; string maxnum; foreach(x; m.arrayof) { string num; real frac, integ; frac = modf(x, integ); if (frac == 0.0f) { num = format("% s", to!long(integ)); if (num.length > width) width = cast(uint)num.length; } else { num = format("% .4f", x); } } auto writer = appender!string(); foreach (x; 0..N) { foreach (y; 0..N) { string s = format("% -*.4f", width, m.arrayof[y * N + x]); uint n = 0; foreach(i, c; s) { if (i < width) { formattedWrite(writer, c.to!string); n++; } } if (y < N-1) formattedWrite(writer, " "); } if (x < N-1) formattedWrite(writer, "\n"); } return writer.data; } /// unittest { import std.string; Matrix2f m1 = matrixf( 1, 0, 0, 1 ); string s = m1.toString; assert(s.startsWith(" 1.0000 0.0000 \n 0.0000 1.0000")); } ================================================ FILE: dlib/math/package.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Linear algebra and calculus * * Description: * dlib.math brings vector and matrix types to D, as well as some calculus * functionality. dlib.math is great as a math library for games, * graphics/physics engines and rendering pipelines. All types are POD and * OpenGL-friendly: you can pass your 4x4 matrices to OpenGL functions directly, * without any conversion. * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math; public { import dlib.math.combinatorics; import dlib.math.complex; import dlib.math.decomposition; import dlib.math.diff; import dlib.math.dual; import dlib.math.dualquaternion; import dlib.math.fft; import dlib.math.hof; import dlib.math.interpolation; import dlib.math.linsolve; import dlib.math.matrix; import dlib.math.quaternion; import dlib.math.sse; import dlib.math.tensor; import dlib.math.transformation; import dlib.math.utils; import dlib.math.vector; } ================================================ FILE: dlib/math/quaternion.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Quaternions * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.quaternion; import std.math; import std.traits; import dlib.math.vector; import dlib.math.matrix; import dlib.math.utils; /** * Quaternion representation */ struct Quaternion(T) { Vector!(T,4) vectorof; alias vectorof this; this(T x, T y, T z, T w) { vectorof = Vector!(T,4)(x, y, z, w); } this(T[4] arr) { vectorof.arrayof = arr; } this(Vector!(T,4) v) { vectorof = v; } this(Vector!(T,3) v, T neww) { vectorof = Vector!(T,4)(v.x, v.y, v.z, neww); } /** * Identity quaternion */ static Quaternion!(T) identity() { return Quaternion!(T)(T(0), T(0), T(0), T(1)); } /** * Quaternion!(T) + Quaternion!(T) */ Quaternion!(T) opBinary(string op)(Quaternion!(T) q) if (op == "+") { return Quaternion!(T)(x + q.x, y + q.y, z + q.z, w + q.w); } /** * Quaternion!(T) += Quaternion!(T) */ Quaternion!(T) opOpAssign(string op)(Quaternion!(T) q) if (op == "+") { this = this + q; return this; } /** * Quaternion!(T) - Quaternion!(T) */ Quaternion!(T) opBinary(string op)(Quaternion!(T) q) if (op == "-") { return Quaternion!(T)(x - q.x, y - q.y, z - q.z, w - q.w); } /** * Quaternion!(T) -= Quaternion!(T) */ Quaternion!(T) opOpAssign(string op)(Quaternion!(T) q) if (op == "-") { this = this - q; return this; } /** * Quaternion!(T) * Quaternion!(T) */ Quaternion!(T) opBinary(string op)(Quaternion!(T) q) if (op == "*") { return Quaternion!(T) ( (x * q.w) + (w * q.x) + (y * q.z) - (z * q.y), (y * q.w) + (w * q.y) + (z * q.x) - (x * q.z), (z * q.w) + (w * q.z) + (x * q.y) - (y * q.x), (w * q.w) - (x * q.x) - (y * q.y) - (z * q.z) ); } /** * Quaternion!(T) *= Quaternion!(T) */ Quaternion!(T) opOpAssign(string op)(Quaternion!(T) q) if (op == "*") { this = this * q; return this; } /** * Quaternion!(T) * T */ Quaternion!(T) opBinary(string op)(T k) if (op == "*") { return Quaternion!(T)(x * k, y * k, z * k, w * k); } /** * Quaternion!(T) *= T */ Quaternion!(T) opOpAssign(string op)(T k) if (op == "*") { x *= k; y *= k; z *= k; w *= k; return this; } /** * T * Quaternion!(T) */ Quaternion!(T) opBinaryRight(string op) (T k) if (op == "*") { return Quaternion!(T)(x * k, y * k, z * k, w * k); } /** * Quaternion!(T) / T */ Quaternion!(T) opBinary(string op)(T k) if (op == "/") { T oneOverK = 1.0 / k; return Quaternion!(T) ( x * oneOverK, y * oneOverK, z * oneOverK, w * oneOverK ); } /** * Quaternion!(T) /= T */ Quaternion!(T) opOpAssign(string op)(T k) if (op == "/") { T oneOverK = 1.0 / k; x *= oneOverK; y *= oneOverK; z *= oneOverK; w *= oneOverK; return this; } /** * Quaternion!(T) * Vector!(T,3) */ Quaternion!(T) opBinary(string op)(Vector!(T,3) v) if (op == "*") { return Quaternion!(T) ( (w * v.x) + (y * v.z) - (z * v.y), (w * v.y) + (z * v.x) - (x * v.z), (w * v.z) + (x * v.y) - (y * v.x), - (x * v.x) - (y * v.y) - (z * v.z) ); } /** * Quaternion!(T) *= Vector!(T,3) */ Quaternion!(T) opOpAssign(string op)(Vector!(T,3) v) if (op == "*") { this = this * v; return this; } /** * Conjugate * A quaternion with the opposite rotation */ Quaternion!(T) conjugate() { return Quaternion!(T)(-x, -y, -z, w); } alias conj = conjugate; /** * Compute the W component of a unit length quaternion */ void computeW() { T t = T(1.0) - (x * x) - (y * y) - (z * z); if (t < 0.0) w = 0.0; else w = -(t.sqrt); } /** * Rotate a point by quaternion */ Vector!(T,3) rotate(Vector!(T,3) v) { Quaternion!(T) qf = this * v * this.conj; return Vector!(T,3)(qf.x, qf.y, qf.z); } Vector!(T,3) opBinaryRight(string op) (Vector!(T,3) v) if (op == "*") { Quaternion!(T) qf = this * v * this.conj; return Vector!(T,3)(qf.x, qf.y, qf.z); } static if (isNumeric!(T)) { /** * Normalized version */ Quaternion!(T) normalized() { Quaternion!(T) q = this; q.normalize(); return q; } /** * Convert to 4x4 matrix */ Matrix!(T,4) toMatrix4x4() const { auto mat = Matrix!(T,4).identity; mat[0] = 1.0 - 2.0 * (y * y + z * z); mat[1] = 2.0 * (x * y + z * w); mat[2] = 2.0 * (x * z - y * w); mat[3] = 0.0; mat[4] = 2.0 * (x * y - z * w); mat[5] = 1.0 - 2.0 * (x * x + z * z); mat[6] = 2.0 * (z * y + x * w); mat[7] = 0.0; mat[8] = 2.0 * (x * z + y * w); mat[9] = 2.0 * (y * z - x * w); mat[10] = 1.0 - 2.0 * (x * x + y * y); mat[11] = 0.0; mat[12] = 0.0; mat[13] = 0.0; mat[14] = 0.0; mat[15] = 1.0; return mat; } /** * Convert to 3x3 matrix */ Matrix!(T,3) toMatrix3x3() const { auto mat = Matrix!(T,3).identity; mat[0] = 1.0 - 2.0 * (y * y + z * z); mat[1] = 2.0 * (x * y + z * w); mat[2] = 2.0 * (x * z - y * w); mat[3] = 2.0 * (x * y - z * w); mat[4] = 1.0 - 2.0 * (x * x + z * z); mat[5] = 2.0 * (z * y + x * w); mat[6] = 2.0 * (x * z + y * w); mat[7] = 2.0 * (y * z - x * w); mat[8] = 1.0 - 2.0 * (x * x + y * y); return mat; } /** * Setup the quaternion to perform a rotation, * given the angular displacement in matrix form */ static Quaternion!(T) fromMatrix(Matrix!(T,4) m) { Quaternion!(T) q; T trace = m.a11 + m.a22 + m.a33 + 1.0; if (trace > 0.0001) { T s = 0.5 / sqrt(trace); q.w = 0.25 / s; q.x = (m.a23 - m.a32) * s; q.y = (m.a31 - m.a13) * s; q.z = (m.a12 - m.a21) * s; } else { if ((m.a11 > m.a22) && (m.a11 > m.a33)) { T s = 0.5 / sqrt(1.0 + m.a11 - m.a22 - m.a33); q.x = 0.25 / s; q.y = (m.a21 + m.a12) * s; q.z = (m.a31 + m.a13) * s; q.w = (m.a32 - m.a23) * s; } else if (m.a22 > m.a33) { T s = 0.5 / sqrt(1.0 + m.a22 - m.a11 - m.a33); q.x = (m.a21 + m.a12) * s; q.y = 0.25 / s; q.z = (m.a32 + m.a23) * s; q.w = (m.a31 - m.a13) * s; } else { T s = 0.5 / sqrt(1.0 + m.a33 - m.a11 - m.a22); q.x = (m.a31 + m.a13) * s; q.y = (m.a32 + m.a23) * s; q.z = 0.25 / s; q.w = (m.a21 - m.a12) * s; } } return q; } /** * Setup the quaternion to perform a rotation, * given the orientation in Euler angles pitch, yaw, roll (in radians) */ static Quaternion!(T) fromEulerAngles(Vector!(T,3) e) { Quaternion!(T) q; T cy = cos(e.z * 0.5); T sy = sin(e.z * 0.5); T cp = cos(e.y * 0.5); T sp = sin(e.y * 0.5); T cr = cos(e.x * 0.5); T sr = sin(e.x * 0.5); q.w = cr * cp * cy + sr * sp * sy; q.x = sr * cp * cy - cr * sp * sy; q.y = cr * sp * cy + sr * cp * sy; q.z = cr * cp * sy - sr * sp * cy; return q; } /** * Setup the Euler angles (pitch, yaw, roll), given a rotation Quaternion. * Returned x, y, z are in radians */ Vector!(T,3) toEulerAngles() { Vector!(T,3) e; // pitch (x-axis rotation) T sinr_cosp = 2 * (w * x + y * z); T cosr_cosp = 1 - 2 * (x * x + y * y); e.x = atan2(sinr_cosp, cosr_cosp); // yaw (y-axis rotation) T sinp = 2 * (w * y - z * x); if (abs(sinp) >= 1.0) e.y = PI / 2.0 * sinp; else e.y = asin(sinp); // roll (z-axis rotation) T siny_cosp = 2 * (w * z + x * y); T cosy_cosp = 1 - 2 * (y * y + z * z); e.z = atan2(siny_cosp, cosy_cosp); return e; } /** * Return the rotation angle (in radians) */ T rotationAngle() { return 2.0 * acos(w); } /** * Return the rotation axis */ Vector!(T,3) rotationAxis() { T s = sqrt(1.0 - (w * w)); if (s <= 0.0f) return Vector!(T,3)(x, y, z); else return Vector!(T,3)(x / s, y / s, z / s); } } } /* * Predefined quaternion type aliases */ /// Alias for single precision Quaternion alias Quaternionf = Quaternion!(float); /// Alias for double precision Quaternion alias Quaterniond = Quaternion!(double); /// unittest { import dlib.math.transformation; Quaternionf q1 = Quaternionf(0.0f, 0.0f, 0.0f, 1.0f); Vector3f v1 = q1.rotate(Vector3f(1.0f, 0.0f, 0.0f)); assert(isAlmostZero(v1 - Vector3f(1.0f, 0.0f, 0.0f))); Quaternionf q2 = Quaternionf.identity; assert(isConsiderZero(q2.x)); assert(isConsiderZero(q2.y)); assert(isConsiderZero(q2.z)); assert(isConsiderZero(q2.w - 1.0f)); Quaternionf q3 = Quaternionf([1.0f, 0.0f, 0.0f, 1.0f]); Quaternionf q4 = Quaternionf([0.0f, 1.0f, 0.0f, 1.0f]); q4 = q3 * q4; assert(q4 == Quaternionf(1, 1, 1, 1)); Vector3f v2 = Vector3f(0, 0, 1); Quaternionf q5 = Quaternionf(v2, 1.0f); q5 *= q5; assert(q5 == Quaternionf(0, 0, 2, 0)); Quaternionf q6 = Quaternionf(Vector4f(1, 0, 0, 1)); Quaternionf q7 = q6 + q6 - Quaternionf(2, 0, 0, 2); assert(q7 == Quaternionf(0, 0, 0, 0)); Quaternionf q8 = Quaternionf(0.5f, 0.5f, 0.5f, 0.0f); q8.computeW(); assert(q8.w == -0.5f); Quaternionf q9 = Quaternionf(0.5f, 0.0f, 0.0f, 0.0f); q9 = q9.normalized; assert(q9 == Quaternionf(1, 0, 0, 0)); assert(q9 * 2.0f == Quaternionf(2, 0, 0, 0)); assert(3.0f * q9 == Quaternionf(3, 0, 0, 0)); assert(q9 / 2.0f == Quaternionf(0.5f, 0, 0, 0)); Quaternionf q10 = Quaternionf(0.0f, 0.0f, 0.0f, 1.0f); Matrix4f m1 = q10.toMatrix4x4; assert(m1 == matrixf( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) ); Matrix3f m2 = q10.toMatrix3x3; assert(m2 == matrixf( 1, 0, 0, 0, 1, 0, 0, 0, 1) ); auto m3 = rotationMatrix!float(0, PI); Quaternionf q11 = Quaternionf.fromMatrix(m3); assert(q11.toMatrix4x4 == m3); Vector3f angles = Vector3f(PI * 0.5f, 0.0f, 0.0f); Quaternionf q12 = Quaternionf.fromEulerAngles(angles); assert(isAlmostZero(q12.toEulerAngles - angles)); assert(isAlmostZero(q12.rotationAxis - Vector3f(1, 0, 0))); assert(isConsiderZero(q12.rotationAngle - angles.x)); } /** * Setup a quaternion to rotate about world axis. * Theta must be in radians */ Quaternion!(T) rotationQuaternion(T)(uint rotaxis, T theta) { Quaternion!(T) res = Quaternion!(T).identity; T thetaOver2 = theta * 0.5; switch (rotaxis) { case Axis.x: res.w = cos(thetaOver2); res.x = sin(thetaOver2); res.y = 0.0; res.z = 0.0; break; case Axis.y: res.w = cos(thetaOver2); res.x = 0.0; res.y = sin(thetaOver2); res.z = 0.0; break; case Axis.z: res.w = cos(thetaOver2); res.x = 0.0; res.y = 0.0; res.z = sin(thetaOver2); break; default: assert(0); } return res; } /** * Setup a quaternion to rotate about specified axis. * Theta must be in radians */ Quaternion!(T) rotationQuaternion(T)(Vector!(T,3) rotaxis, T theta) { Quaternion!(T) res; T thetaOver2 = theta * 0.5; T sinThetaOver2 = sin(thetaOver2); res.w = cos(thetaOver2); res.x = rotaxis.x * sinThetaOver2; res.y = rotaxis.y * sinThetaOver2; res.z = rotaxis.z * sinThetaOver2; return res; } /** * Setup a quaternion to represent rotation * between two unit-length vectors */ Quaternion!(T) rotationBetween(T)(Vector!(T,3) a, Vector!(T,3) b) { Quaternion!(T) q; float d = dot(a, b); float angle = acos(d); Vector!(T,3) axis; if (d < -0.9999) { Vector!(T,3) c; if (a.y != 0.0 || a.z != 0.0) c = Vector!(T,3)(1, 0, 0); else c = Vector!(T,3)(0, 1, 0); axis = cross(a, c); axis.normalize(); q = rotationQuaternion(axis, angle); } else if (d > 0.9999) { q = Quaternion!(T).identity; } else { axis = cross(a, b); axis.normalize(); q = rotationQuaternion(axis, angle); } return q; } /** * Quaternion logarithm */ Quaternion!(T) log(T)(Quaternion!(T) q) { Quaternion!(T) res; res.w = 0.0; if (fabs(q.w) < 1.0) { T theta = acos(q.w); T sin_theta = sin(theta); if (fabs(sin_theta) > 0.00001) { T thetaOverSinTheta = theta / sin_theta; res.x = q.x * thetaOverSinTheta; res.y = q.y * thetaOverSinTheta; res.z = q.z * thetaOverSinTheta; return res; } } res.x = q.x; res.y = q.y; res.z = q.z; return res; } /** * Quaternion exponential */ Quaternion!(T) exp(T) (Quaternion!(T) q) { T theta = sqrt(dot(q, q)); T sin_theta = sin(theta); Quaternion!(T) res; res.w = cos(theta); if (fabs(sin_theta) > 0.00001) { T sinThetaOverTheta = sin_theta / theta; res.x = q.x * sinThetaOverTheta; res.y = q.y * sinThetaOverTheta; res.z = q.z * sinThetaOverTheta; } else { res.x = q.x; res.y = q.y; res.z = q.z; } return res; } /** * Quaternion exponentiation */ Quaternion!(T) pow(T) (Quaternion!(T) q, T exponent) { if (fabs(q.w) > 0.9999) return q; T alpha = acos(q.w); T newAlpha = alpha * exponent; Vector!(T,3) n = Vector!(T,3)(q.x, q.y, q.z); n *= sin(newAlpha) / sin(alpha); return new Quaternion!(T)(n, cos(newAlpha)); } /** * Spherical linear interpolation */ Quaternion!(T) slerp(T)( Quaternion!(T) q0, Quaternion!(T) q1, T t) { if (t <= 0.0) return q0; if (t >= 1.0) return q1; T cosOmega = dot(q0, q1); T q1w = q1.w; T q1x = q1.x; T q1y = q1.y; T q1z = q1.z; if (cosOmega < 0.0) { q1w = -q1w; q1x = -q1x; q1y = -q1y; q1z = -q1z; cosOmega = -cosOmega; } assert (cosOmega < 1.1); T k0, k1; if (cosOmega > 0.9999) { k0 = 1.0 - t; k1 = t; } else { T sinOmega = sqrt(1.0 - (cosOmega * cosOmega)); T omega = atan2(sinOmega, cosOmega); T oneOverSinOmega = 1.0 / sinOmega; k0 = sin((1.0 - t) * omega) * oneOverSinOmega; k1 = sin(t * omega) * oneOverSinOmega; } Quaternion!(T) res = Quaternion!(T) ( (k0 * q0.x) + (k1 * q1x), (k0 * q0.y) + (k1 * q1y), (k0 * q0.z) + (k1 * q1z), (k0 * q0.w) + (k1 * q1w) ); return res; } /** * Spherical cubic interpolation */ Quaternion!(T) squad(T)( Quaternion!(T) q0, Quaternion!(T) qa, Quaternion!(T) qb, Quaternion!(T) q1, T t) { T slerp_t = 2.0 * t * (1.0 - t); Quaternion!(T) slerp_q0 = slerp(q0, q1, t); Quaternion!(T) slerp_q1 = slerp(qa, qb, t); return slerp(slerp_q0, slerp_q1, slerp_t); } /** * Compute intermediate quaternions for building spline segments */ Quaternion!(T) intermediate(T)( Quaternion!(T) qprev, Quaternion!(T) qcurr, Quaternion!(T) qnext, ref Quaternion!(T) qa, ref Quaternion!(T) qb) in { assert (dot(qprev, qprev) <= 1.0001); assert (dot(qcurr, qcurr) <= 1.0001); } do { Quaternion!(T) inv_prev = qprev.conj; Quaternion!(T) inv_curr = qcurr.conj; Quaternion!(T) p0 = inv_prev * qcurr; Quaternion!(T) p1 = inv_curr * qnext; Quaternion!(T) arg = (log(p0) - log(p1)) * 0.25; qa = qcurr * exp( arg); qb = qcurr * exp(-arg); } ================================================ FILE: dlib/math/sse.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov, Alexander Perfilyev Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * SSE-based optimizations for common vector and matrix operations * * Description: * This module implements some frequently used vector and matrix operations using SSE instructions. * * Copyright: Timur Gafarov, Alexander Perfilyev 2015-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov, Alexander Perfilyev */ module dlib.math.sse; import dlib.math.vector; import dlib.math.matrix; version(GNU) { pragma(inline, true); version(X86_Any) { /// Vector addition Vector4f sseAdd4(Vector4f a, Vector4f b) { asm { "movups %[a], %%xmm0 \n" ~ // Load vector a into xmm0 "movups %[b], %%xmm1 \n" ~ // Load vector b into xmm1 "addps %%xmm1, %%xmm0 \n" ~ // Add xmm1 to xmm0 "movups %%xmm0, %[a] \n" // Store the result back in vector a : [a] "+m" (a) // Output operand a, constrained to memory : [b] "m" (b) // Input operand b, constrained to memory : "%xmm0", "%xmm1"; // Clobbered registers } return a; } /// Vector subtraction for GNU D Compiler (using AVX) Vector4f sseSub4(Vector4f a, Vector4f b) { asm { "movups %[a], %%xmm0 \n" ~ // Load vector a into xmm0 "movups %[b], %%xmm1 \n" ~ // Load vector b into xmm1 "subps %%xmm1, %%xmm0 \n" ~ // Subtract xmm1 from xmm0 "movups %%xmm0, %[a] \n" // Store the result back in vector a : [a] "+m" (a) // Output operand a, constrained to memory : [b] "m" (b) // Input operand b, constrained to memory : "%xmm0", "%xmm1"; // Clobbered registers } return a; } /// Vector multiplication for GNU D Compiler (using AVX) Vector4f sseMul4(Vector4f a, Vector4f b) { asm { "movups %[a], %%xmm0 \n" ~ // Load vector a into xmm0 "movups %[b], %%xmm1 \n" ~ // Load vector b into xmm1 "mulps %%xmm1, %%xmm0 \n" ~ // Multiply xmm0 by xmm1 "movups %%xmm0, %[a] \n" // Store the result back in vector a : [a] "+m" (a) // Output operand a, constrained to memory : [b] "m" (b) // Input operand b, constrained to memory : "%xmm0", "%xmm1"; // Clobbered registers } return a; } /// Vector division for GNU D Compiler (using AVX) Vector4f sseDiv4(Vector4f a, Vector4f b) { asm { "movups %[a], %%xmm0 \n" ~ // Load vector a into xmm0 "movups %[b], %%xmm1 \n" ~ // Load vector b into xmm1 "divps %%xmm1, %%xmm0 \n" ~ // Divide xmm0 by xmm1 "movups %%xmm0, %[a] \n" // Store the result back in vector a : [a] "+m" (a) // Output operand a, constrained to memory : [b] "m" (b) // Input operand b, constrained to memory : "%xmm0", "%xmm1"; // Clobbered registers } return a; } /// Vector dot product for GNU D Compiler (using SSE) float sseDot4(Vector4f a, Vector4f b) { asm { "movups %[a], %%xmm0 \n" ~ // Load vector a into xmm0 "movups %[b], %%xmm1 \n" ~ // Load vector b into xmm1 "mulps %%xmm1, %%xmm0 \n" ~ // Multiply xmm0 by xmm1 // Horizontal addition "movhlps %%xmm0, %%xmm1 \n" ~// Copy the high 64 bits to the low 64 bits of xmm1 "addps %%xmm1, %%xmm0 \n" ~ // Add xmm1 to xmm0 "movups %%xmm0, %[a] \n" // Store the result back in vector a : [a] "+m" (a) // Output operand a, constrained to memory : [b] "m" (b) // Input operand b, constrained to memory : "%xmm0", "%xmm1"; // Clobbered registers } return a[0]; } /// Vector cross product for GNU D Compiler (using SSE) Vector4f sseCross3(Vector4f a, Vector4f b) { asm { "movups %[a], %%xmm0 \n" ~ // Load vector a into xmm0 "movups %[b], %%xmm1 \n" ~ // Load vector b into xmm1 "movaps %%xmm0, %%xmm2 \n" ~ // Copy xmm0 to xmm2 "movaps %%xmm1, %%xmm3 \n" ~ // Copy xmm1 to xmm3 "shufps $0xC9, %%xmm0, %%xmm0 \n" ~ // Shuffle xmm0 according to 0xC9 "shufps $0xD2, %%xmm1, %%xmm1 \n" ~ // Shuffle xmm1 according to 0xD2 "shufps $0xD2, %%xmm2, %%xmm2 \n" ~ // Shuffle xmm2 according to 0xD2 "shufps $0xC9, %%xmm3, %%xmm3 \n" ~ // Shuffle xmm3 according to 0xC9 "mulps %%xmm1, %%xmm0 \n" ~ // Multiply xmm0 by xmm1 "mulps %%xmm3, %%xmm2 \n" ~ // Multiply xmm2 by xmm3 "subps %%xmm2, %%xmm0 \n" ~ // Subtract xmm2 from xmm0 "movups %%xmm0, %[a] \n" // Store the result back in vector a : [a] "+m" (a) // Output operand a, constrained to memory : [b] "m" (b) // Input operand b, constrained to memory : "%xmm0", "%xmm1", "%xmm2", "%xmm3"; // Clobbered registers } return a; } /// Matrix multiplication for GNU D Compiler (using SSE) Matrix4x4f sseMulMat4(Matrix4x4f a, Matrix4x4f b) { Matrix4x4f r; Vector4f a_line, b_line, r_line; float _b; uint i, j; Vector4f* _rp; for (i = 0; i < 16; i += 4) { a_line = *cast(Vector4f*)(a.arrayof.ptr); _b = *(b.arrayof.ptr + i); asm { "movups %[a_line], %%xmm0 \n" ~ // Load vector a_line into xmm0 "mov %[_b], %%eax \n" ~ // Move _b into the EAX register "movd %%eax, %%xmm1 \n" ~ // Move EAX into xmm1 "shufps $0, %%xmm1, %%xmm1 \n" ~ // Shuffle xmm1 according to 0 "mulps %%xmm1, %%xmm0 \n" ~ // Multiply xmm0 by xmm1 "movups %%xmm0, %[r_line]" // Store the result in r_line : [r_line] "=m" (r_line) // Output operand r_line, constrained to memory : [a_line] "m" (a_line), [_b] "r" (_b) // Input operands a_line and _b, constrained to memory and register : "%xmm0", "%xmm1", "%eax"; // Clobbered registers } for (j = 1; j < 4; j++) { a_line = *cast(Vector4f*)(a.arrayof.ptr + j * 4); _b = *(b.arrayof.ptr + i + j); asm { "movups %[a_line], %%xmm0 \n" ~ // Load vector a_line into xmm0 "mov %[_b], %%eax \n" ~ // Move _b into the EAX register "movd %%eax, %%xmm1 \n" ~ // Move EAX into xmm1 "shufps $0, %%xmm1, %%xmm1 \n" ~ // Shuffle xmm1 according to 0 "mulps %%xmm1, %%xmm0 \n" ~ // Multiply xmm0 by xmm1 "movups %[r_line], %%xmm2 \n" ~ // Load r_line into xmm2 "addps %%xmm2, %%xmm0 \n" ~ // Add xmm2 to xmm0 "movups %%xmm0, %[r_line]" // Store the result back in r_line : [r_line] "=m" (r_line) // Output and input operands : [a_line] "m" (a_line), [_b] "r" (_b) // Input operand b, constrained to memory : "%xmm0", "%xmm1", "%xmm2", "%eax"; // Clobbered registers } } _rp = cast(Vector4f*)(r.arrayof.ptr + i); version(X86) asm { "mov %[_rp], %%eax \n" ~ // Move _rp into the EAX register "movups %%xmm0,(%%eax)" // Move xmm0 to the memory location pointed by EAX : [_rp] "+r" (_rp) // Output and input operands : // No additional input operands : "%eax", "%xmm0"; // Clobbered registers } version(X86_64) asm { "mov %[_rp], %%rax \n" ~ // Move _rp into the RAX register "movups %%xmm0, (%%rax)" // Move xmm0 to the memory location pointed by RAX : [_rp] "+r" (_rp) // Output and input operands : // No additional input operands : "%rax", "%xmm0"; // Clobbered registers } } return r; } } } version(DMD) { pragma(inline, true): version(X86_Any) { /// Vector addition Vector4f sseAdd4(Vector4f a, Vector4f b) { asm { movups XMM0, a; movups XMM1, b; addps XMM0, XMM1; movups a, XMM0; } return a; } /// Vector subtraction Vector4f sseSub4(Vector4f a, Vector4f b) { asm { movups XMM0, a; movups XMM1, b; subps XMM0, XMM1; movups a, XMM0; } return a; } /// Vector multiplication Vector4f sseMul4(Vector4f a, Vector4f b) { asm { movups XMM0, a; movups XMM1, b; mulps XMM0, XMM1; movups a, XMM0; } return a; } /// Vector division Vector4f sseDiv4(Vector4f a, Vector4f b) { asm { movups XMM0, a; movups XMM1, b; divps XMM0, XMM1; movups a, XMM0; } return a; } /// Vector dot product float sseDot4(Vector4f a, Vector4f b) { asm { movups XMM0, a; movups XMM1, b; mulps XMM0, XMM1; // Horizontal addition movhlps XMM1, XMM0; addps XMM0, XMM1; movups XMM1, XMM0; shufps XMM1, XMM1, 0x55; addps XMM0, XMM1; movups a, XMM0; } return a[0]; } /// Vector cross product Vector4f sseCross3(Vector4f a, Vector4f b) { asm { movups XMM0, a; movups XMM1, b; movaps XMM2, XMM0; movaps XMM3, XMM1; shufps XMM0, XMM0, 0xC9; shufps XMM1, XMM1, 0xD2; shufps XMM2, XMM2, 0xD2; shufps XMM3, XMM3, 0xC9; mulps XMM0, XMM1; mulps XMM2, XMM3; subps XMM0, XMM2; movups a, XMM0; } return a; } /// Matrix multiplication Matrix4x4f sseMulMat4(Matrix4x4f a, Matrix4x4f b) { Matrix4x4f r; Vector4f a_line, b_line, r_line; float _b; uint i, j; Vector4f* _rp; for (i = 0; i < 16; i += 4) { a_line = *cast(Vector4f*)(a.arrayof.ptr); _b = *(b.arrayof.ptr + i); asm { movups XMM0, a_line; mov EAX, _b; movd XMM1, EAX; shufps XMM1, XMM1, 0; mulps XMM0, XMM1; movups r_line, XMM0; } for (j = 1; j < 4; j++) { a_line = *cast(Vector4f*)(a.arrayof.ptr + j * 4); _b = *(b.arrayof.ptr + i + j); asm { movups XMM0, a_line; mov EAX, _b; movd XMM1, EAX; shufps XMM1, XMM1, 0; mulps XMM0, XMM1; movups XMM2, r_line; addps XMM0, XMM2; movups r_line, XMM0; } } _rp = cast(Vector4f*)(r.arrayof.ptr + i); version(X86) asm { mov EAX, _rp; movups [EAX], XMM0; } version(X86_64) asm { mov RAX, _rp; movups [RAX], XMM0; } } return r; } } } ================================================ FILE: dlib/math/tensor.d ================================================ /* Copyright (c) 2016-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * N-dimensional numeric data structure * * Copyright: Timur Gafarov 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.tensor; import std.traits; import std.math; import std.conv; import std.range; import std.format; import dlib.core.tuple; import dlib.core.compound; import dlib.core.memory; T zero(T)() if (isNumeric!T) { return T(0); } size_t calcLen(T...)(T n) { size_t len = 1; foreach(s; n) len *= s; return len; } template NTypeTuple(T, int n) { static if (n <= 0) alias NTypeTuple = Tuple!(); else alias NTypeTuple = Tuple!(NTypeTuple!(T, n-1), T); } enum MaxStaticTensorSize = double.sizeof * 16; // fit 4x4 matrix of doubles /** Generic multi-dimensional array template Description: This template mainly serves as a base for creating various more specialized algebraic objects via encapsulation. Think of Tensor as a backend for e.g. Vector and Matrix. T - element type, usually numeric (float or double) dim - number of dimensions (tensor order): 0 - scalar, 1 - vector, 2 - matrix, 3 - 3D array, (higer dimensions are also possible). sizes - tuple defining sizes for each dimension: 3 - 3-vector, 4,4 - 4x4 matrix, etc. Data storage type (stack or heap) is statically selected: if given size(s) imply data size larger than MaxStaticTensorSize, data is allocated on heap (as dynamic array). Otherwise, data is allocated on stack (as static array). */ template Tensor(T, size_t dim, sizes...) { // TODO: // - External storage // - Component-wise addition, subtraction struct Tensor { private enum size_t _dataLen = calcLen(sizes); alias ElementType = T; enum size_t dimensions = dim; enum size_t order = dim; alias Sizes = sizes; enum bool isTensor = true; enum bool isScalar = (order == 0 && _dataLen == 1); enum bool isVector = (order == 1); enum bool isMatrix = (order == 2); enum bool dynamic = (_dataLen * T.sizeof) > MaxStaticTensorSize; static assert(order == sizes.length, "Illegal size for Tensor"); static if (order > 0) { static assert(sizes.length, "Illegal size for 0-order Tensor"); } static if (isVector) { static assert(sizes.length == 1, "Illegal size for 1st-order Tensor"); } static if (isMatrix) { static assert(sizes.length == 2, "Illegal size for 2nd-order Tensor"); enum size_t rows = sizes[0]; enum size_t cols = sizes[1]; enum bool isSquareMatrix = (rows == cols); static if (isSquareMatrix) { enum size = sizes[0]; } } else { enum bool isSquareMatrix = false; static if (sizes.length > 0) { enum size = sizes[0]; } } /** * Single element constructor */ this(T initVal) { static if (dynamic) { allocate(); } foreach(ref v; data) v = initVal; } /** * Tensor constructor */ this(Tensor!(T, order, sizes) t) { static if (dynamic) { allocate(); } foreach(i, v; t.arrayof) { arrayof[i] = v; } } /** * Tuple constructor */ this(F...)(F components) if (F.length > 1) { static if (dynamic) { allocate(); } foreach(i, v; components) { static if (i < arrayof.length) arrayof[i] = cast(T)v; } } static Tensor!(T, order, sizes) init() { Tensor!(T, order, sizes) res; static if (dynamic) { res.allocate(); } return res; } static Tensor!(T, order, sizes) zero() { Tensor!(T, order, sizes) res; static if (dynamic) { res.allocate(); } foreach(ref v; res.data) v = .zero!T(); return res; } /** * T = Tensor[index] */ auto ref T opIndex(this X)(size_t index) in { assert ((0 <= index) && (index < _dataLen), "Tensor.opIndex: array index out of bounds"); } do { return arrayof[index]; } /** * Tensor[index] = T */ void opIndexAssign(T n, size_t index) in { assert (index < _dataLen, "Tensor.opIndexAssign: array index out of bounds"); } do { arrayof[index] = n; } /** * T = Tensor[i, j, ...] */ T opIndex(I...)(in I indices) const if (I.length == sizes.length) { size_t index = 0; size_t m = 1; foreach(i, ind; indices) { index += ind * m; m *= sizes[i]; } return arrayof[index]; } /** * Tensor[i, j, ...] = T */ T opIndexAssign(I...)(in T t, in I indices) if (I.length == sizes.length) { size_t index = 0; size_t m = 1; foreach(i, ind; indices) { index += ind * m; m *= sizes[i]; } return (arrayof[index] = t); } /** * Tensor = Tensor */ void opAssign (Tensor!(T, order, sizes) t) { static if (dynamic) { allocate(); } foreach(i, v; t.arrayof) { arrayof[i] = v; } } alias Indices = NTypeTuple!(size_t, order); int opApply(scope int delegate(ref T v, Indices indices) dg) { int result = 0; Compound!(Indices) ind; size_t index = 0; while(index < data.length) { result = dg(data[index], ind.tuple); if (result) break; ind[0]++; foreach(i; RangeTuple!(0, order)) { if (ind[i] == sizes[i]) { ind[i] = 0; static if (i < order-1) { ind[i+1]++; } } } index++; } return result; } @property string toString() const { static if (isScalar) { return x.to!string; } else { auto writer = appender!string(); formattedWrite(writer, "%s", arrayof); return writer.data; } } @property size_t length() { return data.length; } @property bool initialized() { return (data.length > 0); } static if (isVector) { private static bool valid(string s) { if (s.length < 2) return false; foreach(c; s) { switch(c) { case 'w', 'a', 'q': if (size < 4) return false; else break; case 'z', 'b', 'p': if (size < 3) return false; else break; case 'y', 'g', 't': if (size < 2) return false; else break; case 'x', 'r', 's': if (size < 1) return false; else break; default: return false; } } return true; } static if (size < 5) { /** * Symbolic element access for vector */ private static string vecElements(string[4] letters) @property { string res; foreach (i; 0..size) { res ~= "T " ~ letters[i] ~ "; "; } return res; } } /** * Swizzling */ template opDispatch(string s) if (valid(s)) { static if (s.length <= 4) { @property auto ref opDispatch(this X)() { auto extend(string s) { while (s.length < 4) s ~= s[$-1]; return s; } enum p = extend(s); enum i = (char c) => ['x':0, 'y':1, 'z':2, 'w':3, 'r':0, 'g':1, 'b':2, 'a':3, 's':0, 't':1, 'p':2, 'q':3][c]; enum i0 = i(p[0]), i1 = i(p[1]), i2 = i(p[2]), i3 = i(p[3]); static if (s.length == 4) return Tensor!(T,1,4)(arrayof[i0], arrayof[i1], arrayof[i2], arrayof[i3]); else static if (s.length == 3) return Tensor!(T,1,3)(arrayof[i0], arrayof[i1], arrayof[i2]); else static if (s.length == 2) return Tensor!(T,1,2)(arrayof[i0], arrayof[i1]); } } } } static if (dynamic) { T[] data; private void allocate() { if (data.length == 0) data = New!(T[])(_dataLen); } void free() { if (data.length) Delete(data); } } else { union { T[_dataLen] data; static if (isScalar) { T x; } static if (isVector) { static if (size < 5) { struct { mixin(vecElements(["x", "y", "z", "w"])); } struct { mixin(vecElements(["r", "g", "b", "a"])); } struct { mixin(vecElements(["s", "t", "p", "q"])); } } } } static if (isScalar) { alias x this; } } alias arrayof = data; } } /** * Tensor product * * Description: * Tensor product of two tensors of order N * and sizes S1 and S2 gives a tensor of order 2N * and sizes (S1,S2). */ auto tensorProduct(T1, T2)(T1 t1, T2 t2) { // TODO: ensure T1, t2 are Tensors // TODO: if T1 and T2 are scalars, use ordinary multiplication // TODO: if T1 and T2 are vectors, use optimized version static assert(T1.dimensions == T2.dimensions); alias T = T1.ElementType; enum order = T1.dimensions + T2.dimensions; alias sizes = Tuple!(T2.Sizes, T1.Sizes); alias TensorType = Tensor!(T, order, sizes); TensorType t; static if (TensorType.dynamic) { t = TensorType.init(); } Compound!(TensorType.Indices) ind; size_t index = 0; while(index < t.data.length) { t.data[index] = t2[ind.tuple[0..$/2]] * t1[ind.tuple[$/2..$]]; ind[0]++; foreach(i; RangeTuple!(0, order)) { if (ind[i] == sizes[i]) { ind[i] = 0; static if (i < order-1) { ind[i+1]++; } } } index++; } return t; } ================================================ FILE: dlib/math/transformation.d ================================================ /* Copyright (c) 2013-2025 Timur Gafarov, Martin Cejp Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Matrix-based geometric transformations * * Copyright: Timur Gafarov 2013-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.transformation; import std.math; import dlib.math.utils; import dlib.math.vector; import dlib.math.matrix; import dlib.math.quaternion; /** * Setup a rotation matrix, given Euler angles in radians */ Matrix!(T,4) fromEuler(T) (Vector!(T,3) v) { auto res = Matrix!(T,4).identity; T cx = cos(v.x); T sx = sin(v.x); T cy = cos(v.y); T sy = sin(v.y); T cz = cos(v.z); T sz = sin(v.z); T sxsy = sx * sy; T cxsy = cx * sy; res.a11 = (cy * cz); res.a12 = (sxsy * cz) + (cx * sz); res.a13 = -(cxsy * cz) + (sx * sz); res.a21 = -(cy * sz); res.a22 = -(sxsy * sz) + (cx * cz); res.a23 = (cxsy * sz) + (sx * cz); res.a31 = (sy); res.a32 = -(sx * cy); res.a33 = (cx * cy); return res; } /** * Setup the Euler angles in radians, given a rotation matrix */ Vector!(T,3) toEuler(T) (Matrix!(T,4) m) do { Vector!(T,3) v; v.y = asin(m.a31); T cy = cos(v.y); T oneOverCosY = 1.0 / cy; if (fabs(cy) > 0.001) { v.x = atan2(-m.a32 * oneOverCosY, m.a33 * oneOverCosY); v.z = atan2(-m.a21 * oneOverCosY, m.a11 * oneOverCosY); } else { v.x = 0.0; v.z = atan2(m.a12, m.a22); } return v; } /** * Right vector of the matrix */ Vector!(T,3) right(T) (Matrix!(T,4) m) do { return Vector!(T,3)(m.a11, m.a21, m.a31); } /** * Up vector of the matrix */ Vector!(T,3) up(T) (Matrix!(T,4) m) do { return Vector!(T,3)(m.a12, m.a22, m.a32); } /** * Forward vector of the matrix */ Vector!(T,3) forward(T) (Matrix!(T,4) m) do { return Vector!(T,3)(m.a13, m.a23, m.a33); } /** * Translation vector of the matrix */ Vector!(T,3) translation(T) (Matrix!(T,4) m) do { return Vector!(T,3)(m.a14, m.a24, m.a34); } /// unittest { Matrix4f tm = matrixf( 1.0f, 0.0f, 0.0f, 3.0f, 0.0f, 1.0f, 0.0f, 5.0f, 0.0f, 0.0f, 1.0f, 2.5f, 0.0f, 0.0f, 0.0f, 1.0f, ); Vector3f t = translation(tm); assert(t == Vector3f(3.0f, 5.0f, 2.5f)); } /** * Scaling vector of the matrix */ Vector!(T,3) scaling(T) (Matrix!(T,4) m) do { T sx = Vector!(T,3)(m.a11, m.a12, m.a13).length; T sy = Vector!(T,3)(m.a21, m.a22, m.a23).length; T sz = Vector!(T,3)(m.a31, m.a32, m.a33).length; return Vector!(T,3)(sx, sy, sz); } /** * Create a matrix to perform a rotation about a world axis * (theta in radians) */ Matrix!(T,4) rotationMatrix(T) (uint rotaxis, T theta) do { auto res = Matrix!(T,4).identity; T s = sin(theta); T c = cos(theta); switch (rotaxis) { case Axis.x: res.a11 = 1.0; res.a12 = 0.0; res.a13 = 0.0; res.a21 = 0.0; res.a22 = c; res.a23 = s; res.a31 = 0.0; res.a32 = -s; res.a33 = c; break; case Axis.y: res.a11 = c; res.a12 = 0.0; res.a13 = -s; res.a21 = 0.0; res.a22 = 1.0; res.a23 = 0.0; res.a31 = s; res.a32 = 0.0; res.a33 = c; break; case Axis.z: res.a11 = c; res.a12 = s; res.a13 = 0.0; res.a21 = -s; res.a22 = c; res.a23 = 0.0; res.a31 = 0.0; res.a32 = 0.0; res.a33 = 1.0; break; default: assert(0); } return res; } /** * Create a translation matrix given a translation vector */ Matrix!(T,4) translationMatrix(T) (Vector!(T,3) v) do { auto res = Matrix!(T,4).identity; res.a14 = v.x; res.a24 = v.y; res.a34 = v.z; return res; } /// unittest { Matrix4f tm = translationMatrix(Vector3f(3.0f, 5.0f, 2.5f)); Vector3f t = translation(tm); assert(t == Vector3f(3.0f, 5.0f, 2.5f)); } /** * Create a matrix to perform scale on each axis */ Matrix!(T,4) scaleMatrix(T) (Vector!(T,3) v) do { auto res = Matrix!(T,4).identity; res.a11 = v.x; res.a22 = v.y; res.a33 = v.z; return res; } /** * Create a combined transformation matrix from translation, rotation, and scaling. * * Params: * t = Translation vector. * r = Rotation quaternion. * s = Scaling vector. * Returns: * The resulting transformation matrix. */ Matrix!(T,4) trsMatrix(T) (Vector!(T,3) t, Quaternion!T r, Vector!(T,3) s) { Matrix!(T,4) res = Matrix!(T,4).identity; Matrix!(T,3) rm = r.toMatrix3x3; res.a11 = rm.a11 * s.x; res.a12 = rm.a12 * s.x; res.a13 = rm.a13 * s.x; res.a21 = rm.a21 * s.y; res.a22 = rm.a22 * s.y; res.a23 = rm.a23 * s.y; res.a31 = rm.a31 * s.z; res.a32 = rm.a32 * s.z; res.a33 = rm.a33 * s.z; res.a14 = t.x; res.a24 = t.y; res.a34 = t.z; return res; } /** * Setup the matrix to perform scale along an arbitrary axis */ Matrix!(T,4) scaleAlongAxisMatrix(T) (Vector!(T,3) scaleAxis, T k) in { assert (fabs (dot(scaleAxis, scaleAxis) - 1.0) < 0.001); } do { auto res = Matrix!(T,4).identity; T a = k - 1.0; T ax = a * scaleAxis.x; T ay = a * scaleAxis.y; T az = a * scaleAxis.z; res.a11 = (ax * scaleAxis.x) + 1.0; res.a22 = (ay * scaleAxis.y) + 1.0; res.a33 = (az * scaleAxis.z) + 1.0; res.a12 = res.a21 = (ax * scaleAxis.y); res.a13 = res.a31 = (ax * scaleAxis.z); res.a23 = res.a32 = (ay * scaleAxis.z); return res; } /** * Create a matrix to perform uniform scale with respect to a point */ Matrix!(T,4) homothetyMatrix(T) (Vector!(T,3) point, T scale) do { auto res = Matrix!(T,4).identity; Vector!(T,3) t = point * (1.0 - scale); res.a11 = scale; res.a14 = t.x; res.a22 = scale; res.a24 = t.y; res.a33 = scale; res.a34 = t.z; return res; } /** * Setup the matrix to perform a shear */ Matrix!(T,4) shearMatrix(T) (uint shearAxis, T s, T t) do { // NOTE: needs test auto res = Matrix!(T,4).identity; switch (shearAxis) { case Axis.x: res.a11 = 1.0; res.a12 = 0.0; res.a13 = 0.0; res.a21 = s; res.a22 = 1.0; res.a23 = 0.0; res.a31 = t; res.a32 = 0.0; res.a33 = 1.0; break; case Axis.y: res.a11 = 1.0; res.a12 = s; res.a13 = 0.0; res.a21 = 0.0; res.a22 = 1.0; res.a23 = 0.0; res.a31 = 0.0; res.a32 = t; res.a33 = 1.0; break; case Axis.z: res.a11 = 1.0; res.a12 = 0.0; res.a13 = s; res.a21 = 0.0; res.a22 = 1.0; res.a23 = t; res.a31 = 0.0; res.a32 = 0.0; res.a33 = 1.0; break; default: assert(0); } return res; } /** * Setup the matrix to perform a projection onto a plane passing * through the origin. The plane is perpendicular to the * unit vector n. */ Matrix!(T,4) projectionMatrix(T) (Vector!(T,3) n) in { assert (fabs(dot(n, n) - 1.0) < 0.001); } do { // NOTE: needs test auto res = Matrix!(T,4).identity; res.a11 = 1.0 - (n.x * n.x); res.a22 = 1.0 - (n.y * n.y); res.a33 = 1.0 - (n.z * n.z); res.a12 = res.a21 = -(n.x * n.y); res.a13 = res.a31 = -(n.x * n.z); res.a23 = res.a32 = -(n.y * n.z); return res; } /** * Setup the matrix to perform a reflection about a plane parallel * to a cardinal plane. */ Matrix!(T,4) reflectionMatrix(T) (Axis reflectionAxis, T k) do { auto res = Matrix!(T,4).identity; switch (reflectionAxis) { case Axis.x: res.a11 = -1.0; res.a21 = 0.0; res.a31 = 0.0; res.a41 = 2.0 * k; res.a12 = 0.0; res.a22 = 1.0; res.a32 = 0.0; res.a42 = 0.0; res.a13 = 0.0; res.a23 = 0.0; res.a33 = 1.0; res.a43 = 0.0; break; case Axis.y: res.a11 = 1.0; res.a21 = 0.0; res.a31 = 0.0; res.a41 = 0.0; res.a12 = 0.0; res.a22 = -1.0; res.a32 = 0.0; res.a42 = 2.0 * k; res.a13 = 0.0; res.a23 = 0.0; res.a33 = 1.0; res.a43 = 0.0; break; case Axis.z: res.a11 = 1.0; res.a21 = 0.0; res.a31 = 0.0; res.a41 = 0.0; res.a12 = 0.0; res.a22 = 1.0; res.a32 = 0.0; res.a42 = 0.0; res.a13 = 0.0; res.a23 = 0.0; res.a33 = -1.0; res.a43 = 2.0 * k; break; default: assert(0); } return res; } /** * Setup the matrix to perform a reflection about an arbitrary plane * through the origin. The unit vector n is perpendicular to the plane. */ Matrix!(T,4) axisReflectionMatrix(T) (Vector!(T,3) n) in { assert (fabs(dot(n, n) - 1.0) < 0.001); } do { auto res = Matrix!(T,4).identity; T ax = -2.0 * n.x; T ay = -2.0 * n.y; T az = -2.0 * n.z; res.a11 = 1.0 + (ax * n.x); res.a22 = 1.0 + (ay * n.y); res.a32 = 1.0 + (az * n.z); res.a12 = res.a21 = (ax * n.y); res.a13 = res.a31 = (ax * n.z); res.a23 = res.a32 = (ay * n.z); return res; } /** * Setup the matrix to perform a "Look At" transformation * like a first person camera */ Matrix!(T,4) lookAtMatrix(T) (Vector!(T,3) eye, Vector!(T,3) center, Vector!(T,3) up) do { auto Result = Matrix!(T,4).identity; auto f = (center - eye).normalized; auto u = (up).normalized; auto s = cross(f, u).normalized; assert(!s.isAlmostZero, "look direction cannot be exactly parallel to the up direction"); u = cross(s, f); Result[0,0] = s.x; Result[0,1] = s.y; Result[0,2] = s.z; Result[1,0] = u.x; Result[1,1] = u.y; Result[1,2] = u.z; Result[2,0] =-f.x; Result[2,1] =-f.y; Result[2,2] =-f.z; Result[0,3] =-dot(s, eye); Result[1,3] =-dot(u, eye); Result[2,3] = dot(f, eye); return Result; } /** * Setup a frustum matrix given the left, right, bottom, top, near, and far * values for the frustum boundaries. */ Matrix!(T,4) frustumMatrix(T) (T l, T r, T b, T t, T n, T f) in { assert (n >= 0.0); assert (f >= 0.0); } do { auto res = Matrix!(T,4).identity; T width = r - l; T height = t - b; T depth = f - n; res.arrayof[0] = (2 * n) / width; res.arrayof[1] = 0.0; res.arrayof[2] = 0.0; res.arrayof[3] = 0.0; res.arrayof[4] = 0.0; res.arrayof[5] = (2 * n) / height; res.arrayof[6] = 0.0; res.arrayof[7] = 0.0; res.arrayof[8] = (r + l) / width; res.arrayof[9] = (t + b) / height; res.arrayof[10]= -(f + n) / depth; res.arrayof[11]= -1.0; res.arrayof[12]= 0.0; res.arrayof[13]= 0.0; res.arrayof[14]= -(2 * f * n) / depth; res.arrayof[15]= 0.0; return res; } /** * Setup a perspective matrix given the field-of-view in the Y direction * in degrees, the aspect ratio of Y/X, and near and far plane distances */ Matrix!(T,4) perspectiveMatrix(T) (T fovY, T aspect, T n, T f) do { auto res = Matrix!(T,4).identity; T angle; T cot; angle = fovY / 2.0; angle = degtorad(angle); cot = cos(angle) / sin(angle); res.arrayof[0] = cot / aspect; res.arrayof[1] = 0.0; res.arrayof[2] = 0.0; res.arrayof[3] = 0.0; res.arrayof[4] = 0.0; res.arrayof[5] = cot; res.arrayof[6] = 0.0; res.arrayof[7] = 0.0; res.arrayof[8] = 0.0; res.arrayof[9] = 0.0; res.arrayof[10]= -(f + n) / (f - n); res.arrayof[11]= -1.0f; //-(2 * f * n) / (f - n); res.arrayof[12]= 0.0; res.arrayof[13]= 0.0; res.arrayof[14]= -(2 * f * n) / (f - n); //-1.0; res.arrayof[15]= 0.0; return res; } /** * Setup an orthographic Matrix4x4 given the left, right, bottom, top, near, * and far values for the frustum boundaries. */ Matrix!(T,4) orthoMatrix(T) (T l, T r, T b, T t, T n, T f) do { auto res = Matrix!(T,4).identity; T width = r - l; T height = t - b; T depth = f - n; res.arrayof[0] = 2.0 / width; res.arrayof[1] = 0.0; res.arrayof[2] = 0.0; res.arrayof[3] = 0.0; res.arrayof[4] = 0.0; res.arrayof[5] = 2.0 / height; res.arrayof[6] = 0.0; res.arrayof[7] = 0.0; res.arrayof[8] = 0.0; res.arrayof[9] = 0.0; res.arrayof[10]= -2.0 / depth; res.arrayof[11]= 0.0; res.arrayof[12]= -(r + l) / width; res.arrayof[13]= -(t + b) / height; res.arrayof[14]= -(f + n) / depth; res.arrayof[15]= 1.0; return res; } /** * Setup an orientation matrix using 3 basis normalized vectors */ Matrix!(T,4) orthoNormalMatrix(T) (Vector!(T,3) xdir, Vector!(T,3) ydir, Vector!(T,3) zdir) do { auto res = Matrix!(T,4).identity; res.arrayof[0] = xdir.x; res.arrayof[4] = ydir.x; res.arrayof[8] = zdir.x; res.arrayof[12] = 0.0; res.arrayof[1] = xdir.y; res.arrayof[5] = ydir.y; res.arrayof[9] = zdir.y; res.arrayof[13] = 0.0; res.arrayof[2] = xdir.z; res.arrayof[6] = ydir.z; res.arrayof[10]= zdir.z; res.arrayof[14] = 0.0; res.arrayof[3] = 0.0; res.arrayof[7] = 0.0; res.arrayof[11]= 0.0; res.arrayof[15] = 1.0; return res; } /** * Setup a matrix that flattens geometry into a plane, * as if it were casting a shadow from a light */ Matrix!(T,4) shadowMatrix(T) (Vector!(T,4) groundplane, Vector!(T,4) lightpos) { T d = dot(groundplane, lightpos); auto res = Matrix!(T,4).identity; res.a11 = d-lightpos.x * groundplane.x; res.a12 = -lightpos.x * groundplane.y; res.a13 = -lightpos.x * groundplane.z; res.a14 = -lightpos.x * groundplane.w; res.a21 = -lightpos.y * groundplane.x; res.a22 = d-lightpos.y * groundplane.y; res.a23 = -lightpos.y * groundplane.z; res.a24 = -lightpos.y * groundplane.w; res.a31 = -lightpos.z * groundplane.x; res.a32 = -lightpos.z * groundplane.y; res.a33 = d-lightpos.z * groundplane.z; res.a34 = -lightpos.z * groundplane.w; res.a41 = -lightpos.w * groundplane.x; res.a42 = -lightpos.w * groundplane.y; res.a43 = -lightpos.w * groundplane.z; res.a44 = d-lightpos.w * groundplane.w; return res; } /** * Setup an orientation matrix using forward direction vector */ Matrix!(T,4) directionToMatrix(T) (Vector!(T,3) zdir) { Vector!(T,3) xdir = Vector!(T,3)(0, 0, 1); Vector!(T,3) ydir; float d = zdir.z; if (d > -0.999999999 && d < 0.999999999) { xdir = xdir - zdir * d; xdir.normalize(); ydir = cross(zdir, xdir); } else { xdir = Vector!(T,3)(zdir.z, 0, -zdir.x); ydir = Vector!(T,3)(0, 1, 0); } auto m = Matrix!(T,4).identity; m.a13 = zdir.x; m.a23 = zdir.y; m.a33 = zdir.z; m.a11 = xdir.x; m.a21 = xdir.y; m.a31 = xdir.z; m.a12 = ydir.x; m.a22 = ydir.y; m.a32 = ydir.z; return m; } /** * Setup an orientation matrix that performs rotation * between two vectors * * Currently this is just a shortcut * for dlib.math.quaternion.rotationBetween */ Matrix!(T,4) rotationBetweenVectors(T) (Vector!(T,3) source, Vector!(T,3) target) { return rotationBetween(source, target).toMatrix4x4; } /// unittest { bool isAlmostZero4(Vector4f v) { float e = 0.002f; return abs(v.x) < e && abs(v.y) < e && abs(v.z) < e && abs(v.w) < e; } // build ModelView (World to Camera) vec3 center = vec3(0.0f, 0.0f, 0.0f); vec3 eye = center + vec3(0.0f, 1.0f, 1.0f); vec3 up = vec3(0.0f, -0.707f, 0.707f); Matrix4f modelView = lookAtMatrix(eye, center, up); // build Projection (Camera to Eye) Matrix4f projection = perspectiveMatrix(45.0f, 16.0f / 9.0f, 1.0f, 100.0f); // compose into one transformation Matrix4f projectionModelView = projection * modelView; vec4 positionInWorld = vec4(0.0f, 0.0f, 0.0f, 1.0f); vec4 transformed1 = positionInWorld * projectionModelView; vec4 transformed2 = (positionInWorld * modelView) * projection; assert(isAlmostZero4(transformed1 - transformed2)); } /** * Affine transformations in 2D space */ Vector!(T,2) affineTransform2D(T)(Vector!(T,2) v, Matrix!(T,3) m) { return Vector!(T,2) ( (v.x * m.a11) + (v.y * m.a12) + m.a13, (v.x * m.a21) + (v.y * m.a22) + m.a23 ); } /** * Translation in 2D space */ Matrix!(T,3) translationMatrix2D(T) (Vector!(T,2) t) do { Matrix!(T,3) res; res.a11 = 1; res.a12 = 0; res.a13 = t.x; res.a21 = 0; res.a22 = 1; res.a23 = t.y; res.a31 = 0; res.a32 = 0; res.a33 = 1; return res; } alias translation2 = translationMatrix2D; /** * Rotation in 2D space */ Matrix!(T,3) rotationMatrix2D(T) (T theta) do { Matrix!(T,3) res; T s = sin(theta); T c = cos(theta); res.a11 = c; res.a12 = s; res.a13 = 0; res.a21 = -s; res.a22 = c; res.a23 = 0; res.a31 = 0; res.a32 = 0; res.a33 = 1; return res; } alias rotation2 = rotationMatrix2D; /** * Scale in 2D space */ Matrix!(T,3) scaleMatrix2D(T) (Vector!(T,2) s) do { Matrix!(T,3) res; res.a11 = s.x; res.a12 = 0; res.a13 = 0; res.a21 = 0; res.a22 = s.y; res.a23 = 0; res.a31 = 0; res.a32 = 0; res.a33 = 1; return res; } alias scale2 = scaleMatrix2D; /** * Homothety (scale with respect to a point) in 2D space */ Matrix!(T,3) homothetyMatrix2D(T) (Vector!(T,2) point, T scale) do { auto res = Matrix!(T,3).identity; Vector!(T,2) t = point * (1.0 - scale); res.a11 = scale; res.a13 = t.x; res.a22 = scale; res.a23 = t.y; return res; } alias homothety2 = homothetyMatrix2D; /// unittest { bool isAlmostZero2(Vector2f v) { float e = 0.002f; return abs(v.x) < e && abs(v.y) < e; } vec2 v = vec2(1, 0); Matrix3f tm = translationMatrix2D(vec2(2, 3)); vec2 vt = affineTransform2D(v, tm); assert(isAlmostZero2(vt - vec2(3, 3))); Matrix3f rm = rotationMatrix2D(cast(float)PI); vt = affineTransform2D(v, rm); assert(isAlmostZero2(vt - vec2(-1, 0))); Matrix3f sm = scaleMatrix2D(vec2(2, 2)); vt = affineTransform2D(v, sm); assert(isAlmostZero2(vt - vec2(2, 0))); } ================================================ FILE: dlib/math/utils.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Utility math functions * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.utils; private { import core.stdc.stdlib; import std.math; } public: /** * Very small value */ enum EPSILON = 0.000001; /** * Axes of Cartesian space */ enum Axis { x = 0, y = 1, z = 2 } /** * Convert degrees to radians */ T degtorad(T) (T angle) nothrow { return (angle / 180.0) * PI; } /** * Convert radians to degrees */ T radtodeg(T) (T angle) nothrow { return (angle / PI) * 180.0; } /** * Convert radians to revolutions */ T radtorev(T)(T angle) nothrow { return angle / (2.0 * PI); } /** * Convert revolutions to radians */ T revtorad(T)(angle) nothrow { return angle * (2.0 * PI); } /** * Find maximum of two values */ T max2(T) (T x, T y) nothrow { return (x > y)? x : y; } /// unittest { assert(max2(2, 1) == 2); } /** * Find minimum of two values */ T min2(T) (T x, T y) nothrow { return (x < y)? x : y; } /// unittest { assert(min2(2, 1) == 1); } /** * Find maximum of three values */ T max3(T) (T x, T y, T z) nothrow { T temp = (x > y)? x : y; return (temp > z) ? temp : z; } /// unittest { assert(max3(3, 2, 1) == 3); } /** * Find minimum of three values */ T min3(T) (T x, T y, T z) nothrow { T temp = (x < y)? x : y; return (temp < z) ? temp : z; } /// unittest { assert(min3(3, 2, 1) == 1); } /** * Limit to given range */ static if (__traits(compiles, (){import std.algorithm: clamp;})) { public import std.algorithm: clamp; } else { T clamp(T) (T v, T minimal, T maximal) nothrow { if (v > minimal) { if (v < maximal) return v; else return maximal; } else return minimal; } } /** * Is less than EPSILON */ bool isConsiderZero(T) (T f) nothrow { return (abs(f) < EPSILON); } /** * Is power of 2 */ bool isPowerOfTwo(T)(T x) nothrow { return (x != 0) && ((x & (x - 1)) == 0); } /// unittest { assert(isPowerOfTwo(16)); assert(!isPowerOfTwo(20)); } /** * Round to next power of 2 */ T nextPowerOfTwo(T) (T k) nothrow { if (k == 0) return 1; k--; for (T i = 1; i < T.sizeof * 8; i <<= 1) k = k | k >> i; return k + 1; } /// unittest { assert(nextPowerOfTwo(0) == 1); assert(nextPowerOfTwo(5) == 8); } /** * Round to next power of 10 */ T nextPowerOfTen(T) (T k) nothrow { return pow(10, cast(int)ceil(log10(k))); } /// unittest { assert(nextPowerOfTen!double(80) == 100); } /** * If at least one element is zero */ bool oneOfIsZero(T) (T[] array...) nothrow { foreach(i, v; array) if (v == 0) return true; return false; } /** * Byte operations */ version (BigEndian) { ushort bigEndian(ushort value) nothrow { return value; } /// unittest { assert(bigEndian(cast(ushort)0x00FF) == 0x00FF); } uint bigEndian(uint value) nothrow { return value; } /// unittest { assert(bigEndian(cast(uint)0x000000FF) == cast(uint)0x000000FF); } ushort networkByteOrder(ushort value) nothrow { return value; } /// unittest { assert(networkByteOrder(cast(ushort)0x00FF) == 0x00FF); } uint networkByteOrder(uint value) nothrow { return value; } /// unittest { assert(networkByteOrder(cast(uint)0x000000FF) == cast(uint)0x000000FF); } } version (LittleEndian) { ushort bigEndian(ushort value) nothrow { return ((value & 0xFF) << 8) | ((value >> 8) & 0xFF); } /// unittest { assert(bigEndian(cast(ushort)0x00FF) == 0xFF00); } uint bigEndian(uint value) nothrow { return value << 24 | (value & 0x0000FF00) << 8 | (value & 0x00FF0000) >> 8 | value >> 24; } /// unittest { assert(bigEndian(cast(uint)0x000000FF) == cast(uint)0xFF000000); } ushort networkByteOrder(ushort value) nothrow { return bigEndian(value); } /// unittest { assert(networkByteOrder(cast(ushort)0x00FF) == 0xFF00); } uint networkByteOrder(uint value) nothrow { return bigEndian(value); } /// unittest { assert(networkByteOrder(cast(uint)0x000000FF) == cast(uint)0xFF000000); } } /** * Returns 16-bit integer n with swapped endianness */ T swapEndian16(T)(T n) { return cast(T)((n >> 8) | (n << 8)); } /// unittest { assert(swapEndian16(cast(ushort)0xFF00) == 0x00FF); } /** * Constructs uint from an array of bytes */ uint bytesToUint(ubyte[4] src) nothrow { return (src[0] << 24 | src[1] << 16 | src[2] << 8 | src[3]); } /// unittest { assert(bytesToUint([0xee, 0x10, 0xab, 0xff]) == 0xee10abff); } /** * Field of view angle Y from X */ T fovYfromX(T) (T xfov, T aspectRatio) nothrow { xfov = degtorad(xfov); T yfov = 2.0 * atan(tan(xfov * 0.5)/aspectRatio); return radtodeg(yfov); } /** * Field of view angle X from Y */ T fovXfromY(T) (T yfov, T aspectRatio) nothrow { yfov = degtorad(yfov); T xfov = 2.0 * atan(tan(yfov * 0.5) * aspectRatio); return radtodeg(xfov); } /** * Sign of a number */ int sign(T)(T x) nothrow { return (x > 0) - (x < 0); } /** * Swap values */ void swap(T)(T* a, T* b) { T c = *a; *a = *b; *b = c; } /** * Is perfect square */ bool isPerfectSquare(float n) nothrow { float r = sqrt(n); return(r * r == n); } /// unittest { assert(isPerfectSquare(64.0f)); } /** * Integer part */ real integer(real v) { real ipart; modf(v, ipart); return ipart; } /// unittest { assert(integer(54.832f) == 54.0f); } /** * Fractional part */ real frac(real v) { real ipart; return modf(v, ipart); } /// unittest { assert(abs(frac(54.832f) - 0.832f) <= EPSILON); } /** * Wraps an arbitrary angle to [-180, +180] degrees range. * * Params: * a = angle in degrees. * Returns: * Wrapped angle in degrees. */ T wrapAngle(T)(T a) { a = fmod(a + 180.0, 360.0); if (a < 0.0) a += 360.0; return a - 180.0; } ================================================ FILE: dlib/math/vector.d ================================================ /* Copyright (c) 2011-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Vectors of Euclidean space * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.math.vector; import std.conv; import std.math; import std.random; import std.range; import std.format; import std.traits; import dlib.core.tuple; import dlib.math.utils; import dlib.math.matrix; /** * Vector representation */ struct Vector(T, int size) { public: /** * Vector constructor. * Supports initializing from vector of arbitrary length and type */ this (T2, int size2)(Vector!(T2, size2) v) { if (v.arrayof.length >= size) { foreach(i; 0..size) arrayof[i] = cast(T)v.arrayof[i]; } else { foreach(i; 0..v.arrayof.length) arrayof[i] = cast(T)v.arrayof[i]; } } /** * Array constructor */ this (A)(A components) if (isDynamicArray!A && !isSomeString!A) { if (components.length >= size) { foreach(i; 0..size) arrayof[i] = cast(T)components[i]; } else { foreach(i; 0..components.length) arrayof[i] = cast(T)components[i]; } } /** * Static array constructor */ this (T2, size_t arrSize)(T2[arrSize] components) { if (components.length >= size) { foreach(i; 0..size) arrayof[i] = cast(T)components[i]; } else { foreach(i; 0..components.length) arrayof[i] = cast(T)components[i]; } } /** * Tuple constructor */ this (F...)(F components) { foreach(i; RangeTuple!(0, size)) { static if (i < components.length) arrayof[i] = cast(T)components[i]; else arrayof[i] = cast(T)components[$-1]; } } /** * String constructor */ this (S)(S str) if (isSomeString!S) { arrayof = parse!(T[size])(str); } /** * Vector!(T,size) = Vector!(T,size2) */ void opAssign(T2, int size2)(Vector!(T2,size2) v) { if (v.arrayof.length >= size) { foreach(i; 0..size) arrayof[i] = cast(T)v.arrayof[i]; } else { foreach(i; 0..v.arrayof.length) arrayof[i] = cast(T)v.arrayof[i]; } } /** * Vector!(T,size) = Vector!(T,size2) for enums */ void opAssign(T2)(T2 ev) if (is(T2 == enum)) { opAssign(cast(OriginalType!T2)ev); } /** * -Vector!(T,size) */ Vector!(T,size) opUnary(string s) () const if (s == "-") do { Vector!(T,size) res; foreach(i; RangeTuple!(0, size)) res.arrayof[i] = -arrayof[i]; return res; } /** * +Vector!(T,size) */ Vector!(T,size) opUnary(string s) () const if (s == "+") do { return Vector!(T,size)(this); } /** * Vector!(T,size) + Vector!(T,size) */ Vector!(T,size) opBinary(string op)(Vector!(T,size) v) const if (op == "+") do { Vector!(T,size) res; foreach(i; RangeTuple!(0, size)) res.arrayof[i] = cast(T)(arrayof[i] + v.arrayof[i]); return res; } /** * Vector!(T,size) - Vector!(T,size) */ Vector!(T,size) opBinary(string op)(Vector!(T,size) v) const if (op == "-") do { Vector!(T,size) res; foreach(i; RangeTuple!(0, size)) res.arrayof[i] = cast(T)(arrayof[i] - v.arrayof[i]); return res; } /** * Vector!(T,size) * Vector!(T,size) */ Vector!(T,size) opBinary(string op)(Vector!(T,size) v) const if (op == "*") do { Vector!(T,size) res; foreach(i; RangeTuple!(0, size)) res.arrayof[i] = cast(T)(arrayof[i] * v.arrayof[i]); return res; } /** * Vector!(T,size) / Vector!(T,size) */ Vector!(T,size) opBinary(string op)(Vector!(T,size) v) const if (op == "/") do { Vector!(T,size) res; foreach(i; RangeTuple!(0, size)) res.arrayof[i] = cast(T)(arrayof[i] / v.arrayof[i]); return res; } /** * Vector!(T,size) + T */ Vector!(T,size) opBinary(string op)(T t) const if (op == "+") do { Vector!(T,size) res; foreach(i; RangeTuple!(0, size)) res.arrayof[i] = cast(T)(arrayof[i] + t); return res; } /** * Vector!(T,size) - T */ Vector!(T,size) opBinary(string op)(T t) const if (op == "-") do { Vector!(T,size) res; foreach(i; RangeTuple!(0, size)) res.arrayof[i] = cast(T)(arrayof[i] - t); return res; } /** * Vector!(T,size) * T */ Vector!(T,size) opBinary(string op)(T t) const if (op == "*") do { Vector!(T,size) res; foreach(i; RangeTuple!(0, size)) res.arrayof[i] = cast(T)(arrayof[i] * t); return res; } /** * T * Vector!(T,size) */ Vector!(T,size) opBinaryRight(string op) (T t) const if (op == "*" && isNumeric!T) do { Vector!(T,size) res; foreach(i; RangeTuple!(0, size)) res.arrayof[i] = cast(T)(arrayof[i] * t); return res; } /** * Vector!(T,size) / T */ Vector!(T,size) opBinary(string op)(T t) const if (op == "/") do { Vector!(T,size) res; foreach(i; RangeTuple!(0, size)) res.arrayof[i] = cast(T)(arrayof[i] / t); return res; } /** * Vector!(T,size) % T */ Vector!(T,size) opBinary(string op, T2) (T2 t) const if (op == "%") do { Vector!(T,size) res; foreach(i; RangeTuple!(0, size)) res.arrayof[i] = cast(T)(arrayof[i] % t); return res; } /** * Vector!(T,size) += Vector!(T,size) */ Vector!(T,size) opOpAssign(string op)(Vector!(T,size) v) if (op == "+") do { foreach(i; RangeTuple!(0, size)) arrayof[i] += v.arrayof[i]; return this; } /** * Vector!(T,size) -= Vector!(T,size) */ Vector!(T,size) opOpAssign(string op)(Vector!(T,size) v) if (op == "-") do { foreach(i; RangeTuple!(0, size)) arrayof[i] -= v.arrayof[i]; return this; } /** * Vector!(T,size) *= Vector!(T,size) */ Vector!(T,size) opOpAssign(string op)(Vector!(T,size) v) if (op == "*") do { foreach(i; RangeTuple!(0, size)) arrayof[i] *= v.arrayof[i]; return this; } /** * Vector!(T,size) /= Vector!(T,size) */ Vector!(T,size) opOpAssign(string op)(Vector!(T,size) v) if (op == "/") do { foreach(i; RangeTuple!(0, size)) arrayof[i] /= v.arrayof[i]; return this; } /** * Vector!(T,size) += T */ Vector!(T,size) opOpAssign(string op)(T t) if (op == "+") do { foreach(i; RangeTuple!(0, size)) arrayof[i] += t; return this; } /** * Vector!(T,size) -= T */ Vector!(T,size) opOpAssign(string op)(T t) if (op == "-") do { foreach(i; RangeTuple!(0, size)) arrayof[i] -= t; return this; } /** * Vector!(T,size) *= T */ Vector!(T,size) opOpAssign(string op)(T t) if (op == "*") do { foreach(i; RangeTuple!(0, size)) arrayof[i] *= t; return this; } /** * Vector!(T,size) /= T */ Vector!(T,size) opOpAssign(string op)(T t) if (op == "/") do { foreach(i; RangeTuple!(0, size)) arrayof[i] /= t; return this; } /** * Vector!(T,size) %= T */ Vector!(T,size) opOpAssign(string op, T2)(T2 t) if (op == "%") do { foreach(i; RangeTuple!(0, size)) arrayof[i] %= t; return this; } /** * T = Vector!(T,size)[index] */ auto ref T opIndex(this X)(size_t index) in { assert ((0 <= index) && (index < size), "Vector!(T,size).opIndex(int index): array index out of bounds"); } do { return arrayof[index]; } /** * Vector!(T,size)[index] = T */ void opIndexAssign(T n, size_t index) in { assert ((0 <= index) && (index < size), "Vector!(T,size).opIndexAssign(int index): array index out of bounds"); } do { arrayof[index] = n; } /** * T[] = Vector!(T,size)[index1..index2] */ auto opSlice(this X)(size_t index1, size_t index2) in { assert ((0 <= index1) || (index1 < 3) || (0 <= index2) || (index2 < 3) || (index1 < index2), "Vector!(T,size).opSlice(int index1, int index2): array index out of bounds"); } do { return arrayof[index1..index2]; } /** * Vector!(T,size)[index1..index2] = T */ T opSliceAssign(T t, size_t index1, size_t index2) in { assert ((0 <= index1) || (index1 < 3) || (0 <= index2) || (index2 < 3) || (index1 < index2), "Vector!(T,size).opSliceAssign(T t, int index1, int index2): array index out of bounds"); } do { arrayof[index1..index2] = t; return t; } /** * T = Vector!(T,size)[] */ auto opSlice(this X)() do { return arrayof[]; } /** * Vector!(T,size)[] = T */ T opSliceAssign(T t) do { foreach(i; RangeTuple!(0, size)) arrayof[i] = t; return t; } static if (isNumeric!(T)) { /** * Get vector length squared */ @property T lengthsqr() const do { T res = 0; foreach (component; arrayof) res += component * component; return res; } /** * Get vector length */ @property T length() const do { static if (isFloatingPoint!T) { T t = 0; foreach (component; arrayof) t += component * component; return sqrt(t); } else { T t = 0; foreach (component; arrayof) t += component * component; return cast(T)sqrt(cast(float)t); } } /** * Set vector length to 1 */ void normalize() do { static if (isFloatingPoint!T) { T lensqr = lengthsqr(); if (lensqr > 0) { T coef = 1.0 / sqrt(lensqr); foreach (ref component; arrayof) component *= coef; } } else { T lensqr = lengthsqr(); if (lensqr > 0) { float coef = 1.0 / sqrt(cast(float)lensqr); foreach (ref component; arrayof) component = cast(T)(component * coef); } } } /** * Return normalized copy */ @property Vector!(T,size) normalized() const do { Vector!(T,size) res = this; res.normalize(); return res; } /** * Return true if all components are zero */ @property bool isZero() const do { foreach(i; RangeTuple!(0, size)) if (arrayof[i] != 0) return false; return true; } /** * Clamp components to min/max value */ void clamp(T minv, T maxv) { foreach (ref v; arrayof) v = .clamp(v, minv, maxv); } } /** * Convert to string */ @property string toString() const do { auto writer = appender!string(); formattedWrite(writer, "%s", arrayof); return writer.data; } /** * Swizzling */ template opDispatch(string s) if (valid(s)) { static if (s.length <= 4) { private static auto extend(string s) { while (s.length < 4) s ~= s[$-1]; return s; } unittest { assert(extend("x") == "xxxx"); } @property auto ref opDispatch(this X)() { enum p = extend(s); enum i = (char c) => ['x':0, 'y':1, 'z':2, 'w':3, 'r':0, 'g':1, 'b':2, 'a':3, 's':0, 't':1, 'p':2, 'q':3][c]; enum i0 = i(p[0]), i1 = i(p[1]), i2 = i(p[2]), i3 = i(p[3]); static if (s.length == 4) return Vector!(T,4)(arrayof[i0], arrayof[i1], arrayof[i2], arrayof[i3]); else static if (s.length == 3) return Vector!(T,3)(arrayof[i0], arrayof[i1], arrayof[i2]); else static if (s.length == 2) return Vector!(T,2)(arrayof[i0], arrayof[i1]); } @property void opDispatch(this X, T2, alias n)(Vector!(T2, n) vec) if (s.length == n) { enum p = extend(s); enum i = (char c) => ['x':0, 'y':1, 'z':2, 'w':3, 'r':0, 'g':1, 'b':2, 'a':3, 's':0, 't':1, 'p':2, 'q':3][c]; enum i0 = i(p[0]), i1 = i(p[1]), i2 = i(p[2]), i3 = i(p[3]); static if (s.length == 4) { arrayof[i3] = vec.arrayof[3]; } static if (s.length >= 3) { arrayof[i2] = vec.arrayof[2]; } static if (s.length >= 2) { arrayof[i1] = vec.arrayof[1]; arrayof[i0] = vec.arrayof[0]; } } } } private static bool valid(string s) { if (s.length < 2) return false; foreach(c; s) { switch(c) { case 'w', 'a', 'q': if (size < 4) return false; else break; case 'z', 'b', 'p': if (size < 3) return false; else break; case 'y', 'g', 't': if (size < 2) return false; else break; case 'x', 'r', 's': if (size < 1) return false; else break; default: return false; } } return true; } unittest { static if (size == 3) { assert(valid("xyz")); assert(valid("rgb")); assert(valid("stp")); assert(!valid("m")); assert(!valid("km")); } else static if (size == 4) { assert(valid("xyzw")); assert(valid("rgba")); assert(valid("stpq")); } } /** * Symbolic element access */ private static string elements(string[4] letters) @property do { string res; foreach (i; 0..size) { res ~= "T " ~ letters[i] ~ "; "; } return res; } unittest { static if (size == 3) { assert(elements(["x", "y", "z", "w"]) == "T x; T y; T z; "); } } /** * Vector components */ union { // Elements as static array T[size] arrayof; static if (size < 5) { /// Elements as x, y, z, w struct { mixin(elements(["x", "y", "z", "w"])); } /// Elements as r, g, b, a struct { mixin(elements(["r", "g", "b", "a"])); } /// Elements as s, t, p, q struct { mixin(elements(["s", "t", "p", "q"])); } } } } /// unittest { { const vec3 a = vec3(10.5f, 20.0f, 33.12345f); const vec3 b = -a; const vec3 c = +a - b; const vec3 d = a * b / c; assert(isAlmostZero(to!vec3(c.toString()) - c)); const vec3 v = vec3(10, 10, 10); const vec3 vRes = (v / 10) * 2 - 1 + 5; assert(isAlmostZero(vRes - vec3(6, 6, 6))); assert(!vRes.isZero); ivec2 ab = ivec2(5, 15); ab += ivec2(20, 30); ab *= 3; assert(ab[0] == 75 && ab[1] == 135); auto len = c.length(); auto lensqr = c.lengthsqr(); auto dist = distance(a, b); auto xy = a[0..1]; auto n = a[]; vec3 v1 = vec3(2.0f, 0.0f, 1.0f); ivec3 v2 = v1; assert(ivec3(v1) == ivec3(2, 0, 1)); vec3 v3 = [0, 2, 3.5]; assert(v3 == vec3(0.0f, 2.0f, 3.5f)); ivec3 v4 = [7, 8, 3]; v4 %= 2; assert(v4 == ivec3(1, 0, 1)); } { Vector3f a = Vector3f(1, 2, 3); Vector2f b = Vector2f(a); assert(b == Vector2f(1, 2)); } { Vector3f a = Vector3f([0, 1]); assert(isNaN(a.z)); } { Vector3f a = Vector3f(0, 1, 2); a += 1; assert(a == Vector3f(1, 2, 3)); a *= 2; assert(a == Vector3f(2, 4, 6)); a -= 1; assert(a == Vector3f(1, 3, 5)); a /= 3; assert(a.y == 1); } { Vector3f a; a[1] = 3; assert(a.y == 3); a[0..3] = 1; assert(a == Vector3f(1, 1, 1)); a[] = 0; assert(a == Vector3f(0, 0, 0)); } { Vector3i a = Vector3i(0, 0, 3); a = a.normalized; assert(a == Vector3i(0, 0, 1)); assert(a.length == 1); } { Vector3f a = Vector3f(0, 0, 0); assert(a.isZero); } { Vector3f a = Vector3f(2, -3, 0); a.clamp(-1, 1); assert(a == Vector3f(1, -1, 0)); } { Vector3f a = Vector3f(2, 5, 7); Vector4f b = Vector4f(a); assert(b == Vector4f(2, 5, 7, float.nan)); } { Vector3f a = Vector3f([0, 1]); assert(a == Vector3f(0, 1, float.nan)); Vector4f b = a.xyy; assert(b == Vector4f(0, 1, 1, float.nan)); } { Vector3f a = Vector3f([0, 1]); a.xz = Vector2f([6, 1]); assert(a == Vector3f([6, 1, 1])); a.zxy = Vector3f([1, 2, 3]); assert(a == Vector3f([2, 3, 1])); } { Vector3f a = Vector3f(1, 2, 3); a = a + 1; assert(a == Vector3f(2, 3, 4)); a = a * 2; assert(a == Vector3f(4, 6, 8)); a = a - 2; assert(a == Vector3f(2, 4, 6)); a = a / 2; assert(a == Vector3f(1, 2, 3)); Vector3f b = Vector3f(3, 2, 1); b += a; assert(b == Vector3f(4, 4, 4)); b *= b; assert(b == Vector3f(16, 16, 16)); b /= Vector3f(8, 4, 2); assert(b == Vector3f(2, 4, 8)); b -= a; assert(b == Vector3f(1, 2, 5)); } { Vector3f v = Vector3f(0, 0, 0); v[0] = 5; v[1] = 2; assert(v == Vector3f(5, 2, 0)); v[1..3] = 12; assert(v == Vector3f(5, 12, 12)); v[] = 0; assert(v == Vector3f(0, 0, 0)); } { Vector4f a = Vector4f(2, 4, 6, 8); Vector4f b = a.wxyz; assert(b == Vector4f(8, 2, 4, 6)); float d = dot(a, b); assert(d == 96.0f); } { Vector2f a = Vector2f(1, 2); Vector2f b = Vector2f(2, 1); float d = dot(a, b); assert(d == 4.0f); } } /** * Dot product */ T dot(T, int size) (Vector!(T,size) a, Vector!(T,size) b) do { static if (size == 1) { return a.x * b.x; } else static if (size == 2) { return ((a.x * b.x) + (a.y * b.y)); } else static if (size == 3) { return ((a.x * b.x) + (a.y * b.y) + (a.z * b.z)); } else { T d = 0; //foreach (i; 0..size) foreach(i; RangeTuple!(0, size)) d += a[i] * b[i]; return d; } } /// unittest { Vector3f v = Vector3f(1, 0, 0); assert(dot(v, v) == 1); Vector3f a = Vector3f(1, 2, 3, 4); assert(dot(a, a) == 14); } /** * Cross product */ Vector!(T,size) cross(T, int size) (Vector!(T,size) a, Vector!(T,size) b) if (size == 3) do { return Vector!(T,size) ( (a.y * b.z) - (a.z * b.y), (a.z * b.x) - (a.x * b.z), (a.x * b.y) - (a.y * b.x) ); } /** * Cross product for 4-vectors */ Vector!(T,size) cross(T, int size) (Vector!(T,size) a, Vector!(T,size) b, Vector!(T,size) c) if (size == 4) do { return Vector!(T,size) ( (a.y * b.z * c.w) - (a.y * b.w * c.z) + (a.z * b.w * c.y) - (a.z * b.y * c.w) + (a.w * b.y * c.z) - (a.w * b.z * c.y), (a.z * b.w * c.x) - (a.z * b.x * c.w) + (a.w * b.x * c.z) - (a.w * b.z * c.x) + (a.x * b.z * c.w) - (a.x * b.w * c.z), (a.w * b.x * c.y) - (a.w * b.y * c.x) + (a.x * b.y * c.w) - (a.x * b.w * c.y) + (a.y * b.w * c.x) - (a.y * b.x * c.w), (a.x * b.y * c.z) - (a.x * b.z * c.y) + (a.y * b.z * c.x) - (a.y * b.x * c.z) + (a.z * b.x * c.y) - (a.z * b.y * c.x) ); } /** * Tensor product */ Matrix!(T,N) tensorProduct(T, size_t N) (Vector!(T,N) u, Vector!(T,N) v) do { Matrix!(T,N) res; foreach(i; 0..N) foreach(j; 0..N) { res[i, j] = u[i] * v[j]; } return res; } alias outerProduct = tensorProduct; /** * Compute normal of a plane from three points */ Vector!(T,3) planeNormal(T) (Vector!(T,3) p1, Vector!(T,3) p2, Vector!(T,3) p3) do { Vector!(T,3) vec1 = Vector!(T,3)(p1 - p2); Vector!(T,3) vec2 = Vector!(T,3)(p1 - p3); Vector!(T,3) result = Vector!(T,3)(cross(vec1,vec2)); result.normalize(); return result; } /// unittest { Vector3f n = planeNormal( Vector3f(0, 0, 0), Vector3f(0, 0, 1), Vector3f(1, 0, 0) ); assert(isAlmostZero(n - Vector3f(0, 1, 0))); } /** * */ void rotateAroundAxis(T) (ref Vector!(T,3) V, Vector!(T,3) P, Vector!(T,3) D, T angle) { T axx,axy,axz,ax1; T ayx,ayy,ayz,ay1; T azx,azy,azz,az1; T u,v,w; T u2,v2,w2; T a,b,c; T sa,ca; sa = sin(angle); ca = cos(angle); u = D.x; v = D.y; w = D.z; u2 = u * u; v2 = v * v; w2 = w * w; a = P.x; b = P.y; c = P.z; axx = u2+(v2+w2)*ca; axy = u*v*(1-ca)-w*sa; axz = u*w*(1-ca)+v*sa; ax1 = a*(v2+w2)-u*(b*v+c*w)+(u*(b*v+c*w)-a*(v2+w2))*ca+(b*w-c*v)*sa; ayx = u*v*(1-ca)+w*sa; ayy = v2+(u2+w2)*ca; ayz = v*w*(1-ca)-u*sa; ay1 = b*(u2+w2)-v*(a*u+c*w)+(v*(a*u+c*w)-b*(u2+w2))*ca+(c*u-a*w)*sa; azx = u*w*(1-ca)-v*sa; azy = v*w*(1-ca)+u*sa; azz = w2+(u2+v2)*ca; az1 = c*(u2+v2)-w*(a*u+b*v)+(w*(a*u+b*v)-c*(u2+v2))*ca+(a*v-b*u)*sa; Vector!(T,3) W; W.x = axx * V.x + axy * V.y + axz * V.z + ax1; W.y = ayx * V.x + ayy * V.y + ayz * V.z + ay1; W.z = azx * V.x + azy * V.y + azz * V.z + az1; V = W; } /** * Compute distance between two 2D points */ T distance(T) (Vector!(T,2) a, Vector!(T,2) b) do { T dx = a.x - b.x; T dy = a.y - b.y; return sqrt((dx * dx) + (dy * dy)); } /** * Compute squared distance between two 2D points */ T distancesqr(T) (Vector!(T,2) a, Vector!(T,2) b) do { T dx = a.x - b.x; T dy = a.y - b.y; return ((dx * dx) + (dy * dy)); } /** * Compute distance between two 3D points */ T distance(T) (Vector!(T,3) a, Vector!(T,3) b) do { T dx = a.x - b.x; T dy = a.y - b.y; T dz = a.z - b.z; return sqrt((dx * dx) + (dy * dy) + (dz * dz)); } /** * Compute squared distance between two 3D points */ T distancesqr(T) (Vector!(T,3) a, Vector!(T,3) b) do { T dx = a.x - b.x; T dy = a.y - b.y; T dz = a.z - b.z; return ((dx * dx) + (dy * dy) + (dz * dz)); } /** * Random unit length 2-vector */ Vector!(T,2) randomUnitVector2(T)() { float azimuth = uniform(0.0, 1.0) * 2 * PI; return Vector!(T,2)(cos(azimuth), sin(azimuth)); } /** * Random unit length 3-vector */ Vector!(T,3) randomUnitVector3(T)() { float z = (2 * uniform(0.0, 1.0)) - 1; Vector!(T,2) planar = randomUnitVector2!(T)() * sqrt(1 - z * z); return Vector!(T,3)(planar.x, planar.y, z); } /** * Spherical linear interpolation * (simple lerp is in dlib.math.interpolation) */ Vector!(T,3) slerp(T) (Vector!(T,3) a, Vector!(T,3) b, T t) { T dp = dot(a, b); dp = clamp(dp, -1.0, 1.0); T theta = acos(dp) * t; Vector!(T,3) relativeVec = b - a * dp; relativeVec.normalize(); return ((a * cos(theta)) + (relativeVec * sin(theta))); } /** * Gradually decrease vector to zero length */ Vector!(T,3) vectorDecreaseToZero(T) (Vector!(T,3) vector, T step) { foreach (ref component; vector.arrayof) { if (component > 0.0) component -= step; if (component < 0.0) component += step; } return vector; } /** * Is all elements almost zero */ bool isAlmostZero(Vector3f v) { return (isConsiderZero(v.x) && isConsiderZero(v.y) && isConsiderZero(v.z)); } /** * */ Vector!(T,3) reflect(T)(Vector!(T,3) I, Vector!(T,3) N) { return I - N * dot(N, I) * 2.0; } /** * */ Vector!(T,3) refract(T)(Vector!(T,3) I, Vector!(T,3) N, T r) { T d = 1.0 - r * r * (1.0 - dot(N, I) * dot(N, I)); if (d < 0.0) return Vector!(T,3)(0.0, 0.0, 0.0); return I * r - N * (r * dot(N, I) + sqrt(d)); } /** * */ Vector!(T,3) faceforward(T)(Vector!(T,3) N, Vector!(T,3) I, Vector!(T,3) Nref) { return dot(Nref, I) < 0.0 ? N : -N; } /** * Predefined vector types */ /// Alias for 2-vector of ints alias Vector2i = Vector!(int, 2); /// Alias for 2-vector of uints alias Vector2u = Vector!(uint, 2); /// Alias for 2-vector of floats alias Vector2f = Vector!(float, 2); /// Alias for 2-vector of doubles alias Vector2d = Vector!(double, 2); /// Alias for 3-vector of ints alias Vector3i = Vector!(int, 3); /// Alias for 3-vector of uints alias Vector3u = Vector!(uint, 3); /// Alias for 3-vector of floats alias Vector3f = Vector!(float, 3); /// Alias for 2-vector of doubles alias Vector3d = Vector!(double, 3); /// Alias for 4-vector of ints alias Vector4i = Vector!(int, 4); /// Alias for 4-vector of uints alias Vector4u = Vector!(uint, 4); /// Alias for 4-vector of floats alias Vector4f = Vector!(float, 4); /// Alias for 4-vector of doubles alias Vector4d = Vector!(double, 4); /** * GLSL-like short aliases */ alias ivec2 = Vector2i; alias uvec2 = Vector2u; alias vec2 = Vector2f; alias dvec2 = Vector2d; alias ivec3 = Vector3i; alias uvec3 = Vector3u; alias vec3 = Vector3f; alias dvec3 = Vector3d; alias ivec4 = Vector4i; alias uvec4 = Vector4u; alias vec4 = Vector4f; alias dvec4 = Vector4d; /** * Axis vectors */ static struct AxisVector { Vector3f x = Vector3f(1.0f, 0.0f, 0.0f); Vector3f y = Vector3f(0.0f, 1.0f, 0.0f); Vector3f z = Vector3f(0.0f, 0.0f, 1.0f); } /** * Vector factory function */ auto vectorf(T...)(T t) if (t.length > 0) { return Vector!(float, t.length)(t); } /** * L-value pseudovector for assignment purposes. * * Examples: * -------------------- * float a, b, c; * lvector(a, b, c) = Vector3f(10, 4, 2); * -------------------- */ auto lvector(T...)(ref T x) { struct Result(T, uint size) { T*[size] arrayof; void opAssign(int size2)(Vector!(T,size2) v) { if (v.arrayof.length >= size) foreach(i; 0..size) *arrayof[i] = v.arrayof[i]; else foreach(i; 0..v.arrayof.length) *arrayof[i] = v.arrayof[i]; } } auto res = Result!(typeof(x[0]), x.length)(); foreach(i, ref v; x) res.arrayof[i] = &v; return res; } ================================================ FILE: dlib/memory/allocator.d ================================================ /* Copyright (c) 2016-2025 Eugene Wissner Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Abstract allocator interface * * Copyright: Eugene Wissner 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Eugene Wissner */ module dlib.memory.allocator; version (unittest) { import dlib.memory : defaultAllocator; } /** * Allocator interface. */ interface Allocator { /** * Allocates $(D_PARAM size) bytes of memory. * * Params: * size = Amount of memory to allocate. * * Returns: The pointer to the new allocated memory. */ void[] allocate(size_t size); /** * Deallocates a memory block. * * Params: * p = A pointer to the memory block to be freed. * * Returns: Whether the deallocation was successful. */ bool deallocate(void[] p); /** * Increases or decreases the size of a memory block. * * Params: * p = A pointer to the memory block. * size = Size of the reallocated block. * * Returns: Whether the reallocation was successful. */ bool reallocate(ref void[] p, size_t size); /** * Returns: The alignment offered. */ @property immutable(uint) alignment() const @safe pure nothrow; } /** * Params: * T = Element type of the array being created. * allocator = The allocator used for getting memory. * array = A reference to the array being changed. * length = New array length. * * Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could * not be reallocated. In the latter */ bool resizeArray(T)(Allocator allocator, ref T[] array, in size_t length) { void[] buf = array; if (!allocator.reallocate(buf, length * T.sizeof)) { return false; } array = cast(T[]) buf; return true; } /// unittest { int[] p; defaultAllocator.resizeArray(p, 20); assert(p.length == 20); defaultAllocator.resizeArray(p, 30); assert(p.length == 30); defaultAllocator.resizeArray(p, 10); assert(p.length == 10); defaultAllocator.resizeArray(p, 0); assert(p is null); } enum bool isFinalizable(T) = is(T == class) || is(T == interface) || hasElaborateDestructor!T || isDynamicArray!T; ================================================ FILE: dlib/memory/arena.d ================================================ /* Copyright (c) 2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * General-purpose region-based memory allocator. * * Copyright: Timur Gafarov 2025. * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.memory.arena; import std.traits; import std.conv; import std.meta; import std.string; import std.uni: isWhite; import core.exception: onOutOfMemoryError; import core.stdc.stdio; import dlib.core.memory; import dlib.core.ownership; import dlib.container.array; /// Represents a single memory block within the `Arena`. struct ArenaBuffer { ubyte[] data; size_t offset; } /// Represents an allocation state of the `ArenaBuffer`. struct AllocationMark { ArenaBuffer* buffer; size_t offset; size_t size; } /** * Arena is an owned allocator that pre-allocates memory in large blocks * (buffers) and distributes it in chunks on demand. * It never frees its buffers; they are freed at once on Arena destruction. * The whole Arena can be reset and reused without freeing. * This allows for efficient memory management for many small objects, * such as strings. * * Arena supports: * - allocating class instances, structures, arrays and strings * - automatic memory alignment * - reusing already allocated memory * - creating marks and rolling back to them */ class Arena: Owner { protected: Array!ArenaBuffer buffers; size_t currentBufferIndex; ArenaBuffer* lastBuffer; size_t lastAllocationSize; public: Array!Object objects; /** * Constructor. * * Params: * bufferSize = Initial buffer size. * owner = Owner object. */ this(size_t bufferSize, Owner owner = null) { super(owner); addBuffer(bufferSize); currentBufferIndex = 0; lastAllocationSize = 0; lastBuffer = null; objects.reserve(1024); } ~this() { release(); } /// Resets arena for reuse. void reset() { if (objects.length) { // Call destructors foreach_reverse(obj; objects) { destroy(obj); } // Don't free the register, just reset its position objects.removeBack(cast(uint)objects.length); } foreach (ref buf; buffers) buf.offset = 0; currentBufferIndex = 0; lastAllocationSize = 0; lastBuffer = null; } /// Releases all allocated buffers. void release() { // Call destructors and free the register foreach_reverse(Object obj; this.objects) { destroy(obj); } objects.free(); foreach(buf; buffers) Delete(buf.data); buffers.free(); currentBufferIndex = 0; lastAllocationSize = 0; lastBuffer = null; } /** * Aligns the specified offset with the given alignment. * * Params: * offset - The original offset. * alignment - The desired alignment (power of two). * * Returns: * The new offset, aligned with alignment. */ size_t alignup(size_t offset, size_t alignment) { return (offset + alignment - 1) & ~(alignment - 1); } /** * Adds a new memory buffer. * * Params: * size - The buffer size in bytes. * * Returns: * A pointer to the created buffer. */ protected ArenaBuffer* addBuffer(size_t size) { if (size == 0) size = 1024; ArenaBuffer buf = { data: New!(ubyte[])(size), offset: 0 }; buffers.append(buf); return &buffers.data[buffers.length - 1]; } /** * Allocates a chunk memory in the arena. * * Params: * size - Chunk size in bytes. * alignment = 1 - Desired alignment. * * Returns: * Slice of allocated memory. */ ubyte[] allocate(size_t size, size_t alignment = 1) { // Fast path: allocate in the current buffer, if possible ArenaBuffer* currentBuffer = &buffers.data[currentBufferIndex]; size_t alignedOffset = alignup(currentBuffer.offset, alignment); if (alignedOffset + size <= currentBuffer.data.length) { ubyte[] res = currentBuffer.data[alignedOffset..alignedOffset+size]; currentBuffer.offset = alignedOffset + size; lastBuffer = currentBuffer; lastAllocationSize = size; return res; } // Try to find an existing buffer with enough space foreach (i, ref buf; buffers.data) { alignedOffset = alignup(buf.offset, alignment); if (alignedOffset + size <= buf.data.length) { currentBufferIndex = i; ubyte[] res = buf.data[alignedOffset..alignedOffset+size]; buf.offset = alignedOffset + size; lastBuffer = &buffers.data[i]; lastAllocationSize = size; return res; } } // No suitable buffer found, allocate a new one currentBuffer = addBuffer(size); currentBufferIndex = buffers.length - 1; ubyte[] res = currentBuffer.data[0..size]; currentBuffer.offset = size; lastBuffer = currentBuffer; lastAllocationSize = size; return res; } /** * Shrinks the last allocation to a new size. * Only allowed if newSize <= lastAllocationSize. * * Params: * newSize - New allocation size. */ void shrinkLastAllocation(size_t newSize) { if (newSize <= lastAllocationSize) { // Move the buffer offset back by the difference lastBuffer.offset -= (lastAllocationSize - newSize); lastAllocationSize = newSize; } } /** * Creates a mark for the last allocation. */ AllocationMark mark() { return AllocationMark(lastBuffer, lastBuffer ? lastBuffer.offset : 0, lastAllocationSize); } /** * Rolls back to the specified mark, reclaiming a memory allocated in the buffer after the mark. * Use with caution: all objects created after the mark are invalidated. * * Params: * mark - Allocation mark to roll back to. */ void rollback(AllocationMark mark) { if (mark.buffer !is null) { lastBuffer = mark.buffer; lastBuffer.offset = mark.offset; } } /** * Creates an object of class T in the arena. * * Params: * args - Arguments to the T constructor. * * Returns: * The created object of type T. */ T create(T, A...) (A args) if (is(T == class)) { enum size = __traits(classInstanceSize, T); ubyte* p = allocate(size + psize, T.alignof).ptr; if (!p) onOutOfMemoryError(); auto memory = p[psize..psize+size]; *cast(size_t*)p = size; T res = emplace!(T, A)(memory, args); objects.append(res); return res; } /** * Creates an instance of structure T in the arena. * * Params: * args - structure constructor arguments. * * Returns: * A pointer to the created structure instance. */ T* create(T, A...) (A args) if (is(T == struct)) { enum size = T.sizeof; ubyte* p = allocate(size + psize, T.alignof).ptr; if (!p) onOutOfMemoryError(); auto memory = p[psize..psize+size]; *cast(size_t*)p = size; return emplace!(T, A)(memory, args); } /** * Creates an array in the arena. * * Params: * length - The length of the array. * * Returns: * A new array of type T. */ T create(T)(size_t length) if (isArray!T) { alias AT = ForeachType!T; size_t size = length * AT.sizeof; ubyte[] mem = allocate(size + psize, AT.alignof); if (!mem.length) onOutOfMemoryError(); T arr = cast(T)mem[psize..psize+size]; foreach(ref v; arr) v = v.init; *cast(size_t*)mem = size; return arr; } /** * Allocates space for a null-terminated array of characters of the specified length. * * Params: * len - The length of the string (excluding the null-terminator). * * Returns: * An array of characters. */ char[] allocateString(size_t len) { char[] space = cast(char[])allocate(len + 1); space[len] = 0; // zero-terminate return space[0..len]; } /** * Copies a string to the arena, appending a null-terminator for C compatibility. * * Params: * str - The original string. * * Returns: * The string stored in the arena. */ string store(string str) { char[] space = allocateString(str.length); space[] = str[]; return cast(string)space; } } T[] cat(T)(Arena arena, T[] a, T[] b) { T[] arr = arena.create!(T[])(a.length + b.length); arr[0..a.length] = a[]; arr[a.length..$] = b[]; return arr; } T[] cat(T)(Arena arena, T[] a, T b) { T[] arr = arena.create!(T[])(a.length + 1); arr[0..a.length] = a[]; arr[a.length] = b; return arr; } T[] cat(T)(Arena arena, T a, T[] b) { T[] arr = arena.create!(T[])(b.length + 1); arr[1..b.length] = b[]; arr[0] = a; return arr; } /** * Concatenates two strings and stores the result in the arena. * * Params: * arena - The memory arena. * a - First string. * b - Second string. * * Returns: * The concatenated string. */ string cat(Arena arena, string a, string b) { char[] space = arena.allocateString(a.length + b.length); space[0..a.length] = a[]; space[a.length..$] = b[]; return cast(string)space; } /** * Concatenates any number of strings and stores the result in the arena. * * Params: * arena - The memory arena. * args - Strings to concatenate. * * Returns: * The concatenated string. */ string cat(Args...)(Arena arena, Args args) if (Args.length > 0 && allSatisfy!(isSomeString, Args)) { size_t totalLen = 0; foreach (s; args) totalLen += s.length; char[] space = arena.allocateString(totalLen); size_t pos = 0; foreach (s; args) { space[pos .. pos + s.length] = s[]; pos += s.length; } return cast(string)space; } /** * Formats a string using the given format string and arguments, storing the result in the arena. * * Params: * arena - The memory arena. * fmt - The format string. * args - Arguments to format. * * Returns: * The formatted string stored in the arena. */ string format(T...)(Arena arena, string fmt, T args) { // Estimate the required size size_t size = 256; while (true) { char[] space = arena.allocateString(size); auto written = snprintf(space.ptr, space.length, fmt.ptr, args); if (written < 0) onOutOfMemoryError(); if (cast(size_t)written < space.length) { // Successfully formatted, shrink to actual size, including null-terminator arena.shrinkLastAllocation(cast(size_t)written + 1); // Return the formatted string return cast(string)space[0..cast(size_t)written]; } else { // Not enough space, try again with a larger buffer size = cast(size_t)written + 1; // Rollback the buffer arena.shrinkLastAllocation(0); } } // Should never get here return ""; } /** * Splits a string by the specified delimiter and stores the parts in the arena. * * Params: * arena - The memory arena. * str - The original string. * delimiter - The delimiter character. * * Returns: * An array of strings. */ string[] split(Arena arena, string str, char delimiter) { size_t count = 1; foreach (c; str) if (c == delimiter) count++; auto result = arena.create!(string[])(count); size_t index = 0; size_t start = 0; size_t numChars = 0; for (size_t i = 0; i < str.length; i++) { if (str[i] == delimiter && numChars > 0) { string part = arena.store(str[start..start+numChars]); result[index++] = part; start = i + 1; numChars = 0; } else numChars++; } if (numChars > 0) { string part = arena.store(str[start..start+numChars]); result[index++] = part; } return result; } /** * Joins an array of strings with the specified separator and stores the result in the arena. * * Params: * arena - The memory arena. * parts - The array of strings to join. * sep - The separator string. * * Returns: * The joined string. */ string join(Arena arena, string[] parts, string sep) { if (parts.length == 0) return ""; size_t totalLen = (parts.length - 1) * sep.length; foreach (p; parts) totalLen += p.length; char[] buf = arena.allocateString(totalLen); size_t pos = 0; foreach (i, p; parts) { if (i > 0) { buf[pos..pos+sep.length] = sep[]; pos += sep.length; } buf[pos..pos+p.length] = p[]; pos += p.length; } return cast(string)buf; } /** * Replaces all occurrences of a substring with another substring in the given string, * storing the result in the arena. * * Params: * arena - The memory arena. * str - The original string. * strFrom - The substring to replace. * strTo - The replacement substring. * * Returns: * The modified string with replacements. */ string replace(Arena arena, string str, string strFrom, string strTo) { if (strFrom.length == 0) return arena.store(str); size_t count = 0, pos = 0; while ((pos = str.indexOf(strFrom, pos)) != -1) { ++count; pos += strFrom.length; } if (count == 0) return arena.store(str); size_t newLen = str.length + count * (strTo.length - strFrom.length); char[] buf = arena.allocateString(newLen); size_t src = 0, dst = 0; while (src < str.length) { auto idx = str.indexOf(strFrom, src); if (idx == -1) { buf[dst..dst+str.length-src] = str[src..$]; break; } buf[dst..dst+idx-src] = str[src..idx]; dst += idx - src; buf[dst..dst+strTo.length] = strTo[]; dst += strTo.length; src = idx + strFrom.length; } return cast(string)buf; } /** * Trims whitespace from both ends of the string and stores the result in the arena. * * Params: * arena - The memory arena. * s - The original string. * * Returns: * The trimmed string. */ string trim(Arena arena, string s) { size_t start = 0, end = s.length; while (start < end && isWhite(s[start])) ++start; while (end > start && isWhite(s[end-1])) --end; return arena.store(s[start .. end]); } /** * Duplicates an array into the arena. * * Params: * arena - The memory arena. * arr - The original array. * * Returns: * A copy of the array stored in the arena. */ T[] dup(T)(Arena arena, const(T)[] arr) { auto copy = arena.create!(T[])(arr.length); copy[] = arr[]; return copy; } /** * Duplicates an array into the arena as an immutable array. * * Params: * arena - The memory arena. * arr - The original array. * * Returns: * An immutable copy of the array stored in the arena. */ immutable(T)[] idup(T)(Arena arena, const(T)[] arr) { return cast(immutable(T)[])dup(arena, arr); } ================================================ FILE: dlib/memory/gcallocator.d ================================================ /* Copyright (c) 2017-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Allocator based on D's built-in garbage collector * * Copyright: Timur Gafarov 2017-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.memory.gcallocator; import core.exception; import core.memory; import std.algorithm.comparison; import dlib.memory.allocator; /** * Allocator based on D's built-in garbage collector */ class GCallocator: Allocator { void[] allocate(size_t size) { return GC.malloc(size)[0..size]; } bool deallocate(void[] p) { GC.free(p.ptr); return true; } bool reallocate(ref void[] p, size_t size) { GC.realloc(p.ptr, size); return true; } @property immutable(uint) alignment() const { return cast(uint) max(double.alignof, real.alignof); } static @property GCallocator instance() nothrow { if (instance_ is null) { immutable size = __traits(classInstanceSize, GCallocator); void* p = GC.malloc(size); if (p is null) { onOutOfMemoryError(); } p[0..size] = typeid(GCallocator).initializer[]; instance_ = cast(GCallocator)p[0..size].ptr; } return instance_; } private static __gshared GCallocator instance_; } ================================================ FILE: dlib/memory/mallocator.d ================================================ /* Copyright (c) 2016-2025 Eugene Wissner Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Allocator based on malloc/realloc/free * * Copyright: Eugene Wissner 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Eugene Wissner */ module dlib.memory.mallocator; import dlib.memory.allocator; import core.exception; import core.stdc.stdlib; import std.algorithm.comparison; /** * Wrapper for malloc/realloc/free from the C standard library. */ class Mallocator: Allocator { /** * Allocates $(D_PARAM size) bytes of memory. * * Params: * size = Amount of memory to allocate. * * Returns: The pointer to the new allocated memory. */ void[] allocate(size_t size) { if (!size) { return null; } auto p = malloc(size); if (!p) { onOutOfMemoryError(); } return p[0..size]; } /// unittest { auto p = Mallocator.instance.allocate(20); assert(p.length == 20); Mallocator.instance.deallocate(p); } /** * Deallocates a memory block. * * Params: * p = A pointer to the memory block to be freed. * * Returns: Whether the deallocation was successful. */ bool deallocate(void[] p) { if (p !is null) { free(p.ptr); } return true; } /// unittest { void[] p; assert(Mallocator.instance.deallocate(p)); p = Mallocator.instance.allocate(10); assert(Mallocator.instance.deallocate(p)); } /** * Increases or decreases the size of a memory block. * * Params: * p = A pointer to the memory block. * size = Size of the reallocated block. * * Returns: Whether the reallocation was successful. */ bool reallocate(ref void[] p, size_t size) { if (!size) { deallocate(p); p = null; return true; } else if (p is null) { p = allocate(size); return true; } auto r = realloc(p.ptr, size); if (!r) { onOutOfMemoryError(); } p = r[0..size]; return true; } /// unittest { void[] p; Mallocator.instance.reallocate(p, 20); assert(p.length == 20); Mallocator.instance.reallocate(p, 30); assert(p.length == 30); Mallocator.instance.reallocate(p, 10); assert(p.length == 10); Mallocator.instance.reallocate(p, 0); assert(p is null); } /** * Returns: The alignment offered. */ @property immutable(uint) alignment() const { return cast(uint) max(double.alignof, real.alignof); } /** * Static allocator instance and initializer. * * Returns: The global $(D_PSYMBOL Allocator) instance. */ static @property Mallocator instance() @nogc nothrow { if (instance_ is null) { immutable size = __traits(classInstanceSize, Mallocator); void* p = malloc(size); if (p is null) { onOutOfMemoryError(); } p[0..size] = typeid(Mallocator).initializer[]; instance_ = cast(Mallocator) p[0..size].ptr; } return instance_; } /// @nogc nothrow unittest { assert(instance is instance); } private static __gshared Mallocator instance_; } ================================================ FILE: dlib/memory/mmappool.d ================================================ /* Copyright (c) 2016-2025 Eugene Wissner Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Fast block-based allocator * * Copyright: Eugene Wissner 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Eugene Wissner */ module dlib.memory.mmappool; import dlib.memory.allocator; import core.atomic; import core.exception; version (Posix) { import core.stdc.errno; import core.sys.posix.sys.mman; import core.sys.posix.unistd; } else version (Windows) { import core.sys.windows.winbase; import core.sys.windows.windows; } /** * This allocator allocates memory in regions (multiple of 4 KB for example). * Each region is then splitted in blocks. So it doesn't request the memory * from the operating system on each call, but only if there are no large * enough free blocks in the available regions. * Deallocation works in the same way. Deallocation doesn't immediately * gives the memory back to the operating system, but marks the appropriate * block as free and only if all blocks in the region are free, the complete * region is deallocated. * * ---------------------------------------------------------------------------- * | | | | | || | | | * | |prev <----------- | || | | | * | R | B | | B | || R | B | | * | E | L | | L | next E | L | | * | G | O | DATA | O | FREE ---> G | O | DATA | * | I | C | | C | <--- I | C | | * | O | K | | K | prev O | K | | * | N | -----------> next| || N | | | * | | | | | || | | | * ---------------------------------------------------------------------------- * * TODO: * $(UL * $(LI Thread safety (core.atomic.cas)) * $(LI If two neighbour blocks are free, they can be merged) * $(LI Reallocation shoud check if there is enough free space in the * next block instead of always moving the memory) * $(LI Make 64 KB regions mininmal region size on Linux) * ) */ class MmapPool: Allocator { static initialize() { version (Posix) { pageSize = sysconf(_SC_PAGE_SIZE); } else version (Windows) { SYSTEM_INFO si; GetSystemInfo(&si); pageSize = si.dwPageSize; } } /** * Allocates $(D_PARAM size) bytes of memory. * * Params: * size = Amount of memory to allocate. * * Returns: The pointer to the new allocated memory. */ void[] allocate(size_t size) @nogc @trusted nothrow { if (!size) { return null; } immutable dataSize = addAlignment(size); void* data = findBlock(dataSize); if (data is null) { data = initializeRegion(dataSize); } return data is null ? null : data[0..size]; } /// @nogc @safe nothrow unittest { auto p = MmapPool.instance.allocate(20); assert(p); MmapPool.instance.deallocate(p); } /** * Search for a block large enough to keep $(D_PARAM size) and split it * into two blocks if the block is too large. * * Params: * size = Minimum size the block should have. * * Returns: Data the block points to or $(D_KEYWORD null). */ private void* findBlock(size_t size) @nogc nothrow { Block block1; RegionLoop: for (auto r = head; r !is null; r = r.next) { block1 = cast(Block) (cast(void*) r + regionEntrySize); do { if (block1.free && block1.size >= size) { break RegionLoop; } } while ((block1 = block1.next) !is null); } if (block1 is null) { return null; } else if (block1.size >= size + alignment + blockEntrySize) { // Split the block if needed Block block2 = cast(Block) (cast(void*) block1 + blockEntrySize + size); block2.prev = block1; if (block1.next is null) { block2.next = null; } else { block2.next = block1.next.next; } block1.next = block2; block1.free = false; block2.free = true; block2.size = block1.size - blockEntrySize - size; block1.size = size; block2.region = block1.region; //atomicOp!"+="(block1.region.blocks, 1); // atomicOp works only with shared data ++block1.region.blocks; } else { block1.free = false; //atomicOp!"+="(block1.region.blocks, 1); // atomicOp works only with shared data ++block1.region.blocks; } return cast(void*) block1 + blockEntrySize; } /** * Deallocates a memory block. * * Params: * p = A pointer to the memory block to be freed. * * Returns: Whether the deallocation was successful. */ bool deallocate(void[] p) @nogc @trusted nothrow { if (p is null) { return true; } Block block = cast(Block) (p.ptr - blockEntrySize); if (block.region.blocks <= 1) { if (block.region.prev !is null) { block.region.prev.next = block.region.next; } else // Replace the list head. It is being deallocated { head = block.region.next; } if (block.region.next !is null) { block.region.next.prev = block.region.prev; } version (Posix) { return munmap(cast(void*) block.region, block.region.size) == 0; } version (Windows) { return VirtualFree(cast(void*) block.region, 0, MEM_RELEASE) == 0; } } else { block.free = true; //atomicOp!"-="(block.region.blocks, 1); // atomicOp works only with shared data --block.region.blocks; return true; } } /// @nogc @safe nothrow unittest { auto p = MmapPool.instance.allocate(20); assert(MmapPool.instance.deallocate(p)); } /** * Increases or decreases the size of a memory block. * * Params: * p = A pointer to the memory block. * size = Size of the reallocated block. * * Returns: Whether the reallocation was successful. */ bool reallocate(ref void[] p, size_t size) @nogc @trusted nothrow { void[] reallocP; if (size == p.length) { return true; } else if (size > 0) { reallocP = allocate(size); if (reallocP is null) { return false; } } if (p !is null) { if (size > p.length) { reallocP[0..p.length] = p[0..$]; } else if (size > 0) { reallocP[0..size] = p[0..size]; } deallocate(p); } p = reallocP; return true; } /// @nogc nothrow unittest { void[] p; MmapPool.instance.reallocate(p, 10 * int.sizeof); (cast(int[]) p)[7] = 123; assert(p.length == 40); MmapPool.instance.reallocate(p, 8 * int.sizeof); assert(p.length == 32); assert((cast(int[]) p)[7] == 123); MmapPool.instance.reallocate(p, 20 * int.sizeof); (cast(int[]) p)[15] = 8; assert(p.length == 80); assert((cast(int[]) p)[15] == 8); assert((cast(int[]) p)[7] == 123); MmapPool.instance.reallocate(p, 8 * int.sizeof); assert(p.length == 32); assert((cast(int[]) p)[7] == 123); MmapPool.instance.deallocate(p); } /** * Static allocator instance and initializer. * * Returns: Global $(D_PSYMBOL MmapPool) instance. */ static @property MmapPool instance() @nogc @trusted nothrow { if (instance_ is null) { initialize(); immutable instanceSize = addAlignment(__traits(classInstanceSize, MmapPool)); Region head; // Will become soon our region list head void* data = initializeRegion(instanceSize, head); if (data !is null) { data[0..instanceSize] = typeid(MmapPool).initializer[]; instance_ = cast(MmapPool) data; instance_.head = head; } } return instance_; } /// @nogc @safe nothrow unittest { assert(instance is instance); } /** * Initializes a region for one element. * * Params: * size = Aligned size of the first data block in the region. * head = Region list head. * * Returns: A pointer to the data. */ pragma(inline) private static void* initializeRegion(size_t size, ref Region head) @nogc nothrow { immutable regionSize = calculateRegionSize(size); version (Posix) { void* p = mmap(null, regionSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (p is MAP_FAILED) { if (errno == ENOMEM) { onOutOfMemoryError(); } return null; } } else version (Windows) { void* p = VirtualAlloc(null, regionSize, MEM_COMMIT, PAGE_READWRITE); if (p is null) { if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY) { onOutOfMemoryError(); } return null; } } Region region = cast(Region) p; region.blocks = 1; region.size = regionSize; // Set the pointer to the head of the region list if (head !is null) { head.prev = region; } region.next = head; region.prev = null; head = region; // Initialize the data block void* memoryPointer = p + regionEntrySize; Block block1 = cast(Block) memoryPointer; block1.size = size; block1.free = false; // It is what we want to return void* data = memoryPointer + blockEntrySize; // Free block after data memoryPointer = data + size; Block block2 = cast(Block) memoryPointer; block1.prev = block2.next = null; block1.next = block2; block2.prev = block1; block2.size = regionSize - size - regionEntrySize - blockEntrySize * 2; block2.free = true; block1.region = block2.region = region; return data; } /// Ditto. private void* initializeRegion(size_t size) @nogc nothrow { return initializeRegion(size, head); } /** * Params: * x = Space to be aligned. * * Returns: Aligned size of $(D_PARAM x). */ pragma(inline) private static immutable(size_t) addAlignment(size_t x) @nogc @safe pure nothrow out (result) { assert(result > 0); } do { return (x - 1) / alignment_ * alignment_ + alignment_; } /** * Params: * x = Required space. * * Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)). */ pragma(inline) private static immutable(size_t) calculateRegionSize(size_t x) @nogc nothrow out (result) { assert(result > 0); } do { x += regionEntrySize + blockEntrySize * 2; return x / pageSize * pageSize + pageSize; } @property immutable(uint) alignment() const @nogc @safe pure nothrow { return alignment_; } private enum alignment_ = 8; private static __gshared MmapPool instance_; private static __gshared size_t pageSize; private struct RegionEntry { Region prev; Region next; uint blocks; size_t size; } private alias Region = RegionEntry*; private enum regionEntrySize = 32; private Region head; private struct BlockEntry { Block prev; Block next; bool free; size_t size; Region region; } private alias Block = BlockEntry*; private enum blockEntrySize = 40; } ================================================ FILE: dlib/memory/package.d ================================================ /* Copyright (c) 2016-2025 Eugene Wissner Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Allocators and memory management functions * * Copyright: Eugene Wissner 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Eugene Wissner */ module dlib.memory; public { import dlib.memory.allocator; import dlib.memory.gcallocator; import dlib.memory.mallocator; import dlib.memory.mmappool; import dlib.memory.arena; import std.experimental.allocator: make, dispose, shrinkArray, expandArray, makeArray, dispose; } Allocator allocator; /// Get default allocator (Mallocator) @property Allocator defaultAllocator() { if (allocator is null) { allocator = Mallocator.instance; } return allocator; } ================================================ FILE: dlib/network/errno.d ================================================ /* Copyright (c) 2020-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ module dlib.network.errno; public import core.stdc.errno; version(Windows) { import core.sys.windows.winsock2; enum: int { ECONNABORTED = core.sys.windows.winsock2.ECONNABORTED, ENOBUFS = core.sys.windows.winsock2.ENOBUFS, EOPNOTSUPP = core.sys.windows.winsock2.EOPNOTSUPP, EPROTONOSUPPORT = core.sys.windows.winsock2.EPROTONOSUPPORT, EPROTOTYPE = core.sys.windows.winsock2.EPROTOTYPE, ESOCKTNOSUPPORT = core.sys.windows.winsock2.ESOCKTNOSUPPORT, ETIMEDOUT = core.sys.windows.winsock2.ETIMEDOUT, EWOULDBLOCK = core.sys.windows.winsock2.EWOULDBLOCK } } else version (OSX) { version = MacBSD; } else version (iOS) { version = MacBSD; } else version (FreeBSD) { version = MacBSD; } else version (OpenBSD) { version = MacBSD; } else version (DragonFlyBSD) { version = MacBSD; } version (MacBSD) { enum: int { ESOCKTNOSUPPORT = 44 } } ================================================ FILE: dlib/network/package.d ================================================ /* Copyright (c) 2016-2025 Eugene Wissner, Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Networking and web functionality * * Copyright: Eugene Wissner, Timur Gafarov 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Eugene Wissner, Timur Gafarov */ module dlib.network; public { import dlib.network.socket; import dlib.network.url; } ================================================ FILE: dlib/network/socket.d ================================================ /* Copyright (c) 2016-2025 Eugene Wissner Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Platform-independent socket API * * Copyright: Eugene Wissner 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Eugene Wissner */ module dlib.network.socket; import dlib.memory; import err = dlib.network.errno; import core.time; import std.algorithm.comparison; import std.algorithm.searching; public import std.socket: AddressException, socket_t, Linger, SocketOptionLevel, SocketType, AddressFamily, AddressInfo, SocketOption; import std.traits; import std.typecons; version (Posix) { import core.sys.posix.fcntl; import core.sys.posix.netdb; import core.sys.posix.netinet.in_; import core.sys.posix.sys.socket; import core.sys.posix.sys.time; import core.sys.posix.unistd; private enum SOCKET_ERROR = -1; } else version (Windows) { //import dlib.async.iocp; import core.sys.windows.winbase; import core.sys.windows.windef; import core.sys.windows.basetyps; import core.sys.windows.mswsock; import core.sys.windows.winbase; import core.sys.windows.windef; import core.sys.windows.winsock2; enum : uint { IOC_UNIX = 0x00000000, IOC_WS2 = 0x08000000, IOC_PROTOCOL = 0x10000000, IOC_VOID = 0x20000000, /// No parameters. IOC_OUT = 0x40000000, /// Copy parameters back. IOC_IN = 0x80000000, /// Copy parameters into. IOC_VENDOR = 0x18000000, IOC_INOUT = (IOC_IN | IOC_OUT), /// Copy parameter into and get back. } template _WSAIO(int x, int y) { enum _WSAIO = IOC_VOID | x | y; } template _WSAIOR(int x, int y) { enum _WSAIOR = IOC_OUT | x | y; } template _WSAIOW(int x, int y) { enum _WSAIOW = IOC_IN | x | y; } template _WSAIORW(int x, int y) { enum _WSAIORW = IOC_INOUT | x | y; } alias SIO_ASSOCIATE_HANDLE = _WSAIOW!(IOC_WS2, 1); alias SIO_ENABLE_CIRCULAR_QUEUEING = _WSAIO!(IOC_WS2, 2); alias SIO_FIND_ROUTE = _WSAIOR!(IOC_WS2, 3); alias SIO_FLUSH = _WSAIO!(IOC_WS2, 4); alias SIO_GET_BROADCAST_ADDRESS = _WSAIOR!(IOC_WS2, 5); alias SIO_GET_EXTENSION_FUNCTION_POINTER = _WSAIORW!(IOC_WS2, 6); alias SIO_GET_QOS = _WSAIORW!(IOC_WS2, 7); alias SIO_GET_GROUP_QOS = _WSAIORW!(IOC_WS2, 8); alias SIO_MULTIPOINT_LOOPBACK = _WSAIOW!(IOC_WS2, 9); alias SIO_MULTICAST_SCOPE = _WSAIOW!(IOC_WS2, 10); alias SIO_SET_QOS = _WSAIOW!(IOC_WS2, 11); alias SIO_SET_GROUP_QOS = _WSAIOW!(IOC_WS2, 12); alias SIO_TRANSLATE_HANDLE = _WSAIORW!(IOC_WS2, 13); alias SIO_ROUTING_INTERFACE_QUERY = _WSAIORW!(IOC_WS2, 20); alias SIO_ROUTING_INTERFACE_CHANGE = _WSAIOW!(IOC_WS2, 21); alias SIO_ADDRESS_LIST_QUERY = _WSAIOR!(IOC_WS2, 22); alias SIO_ADDRESS_LIST_CHANGE = _WSAIO!(IOC_WS2, 23); alias SIO_QUERY_TARGET_PNP_HANDLE = _WSAIOR!(IOC_WS2, 24); alias SIO_NSP_NOTIFY_CHANGE = _WSAIOW!(IOC_WS2, 25); private alias GROUP = uint; enum { WSA_FLAG_OVERLAPPED = 0x01, MAX_PROTOCOL_CHAIN = 7, WSAPROTOCOL_LEN = 255, } struct WSAPROTOCOLCHAIN { int ChainLen; DWORD[MAX_PROTOCOL_CHAIN] ChainEntries; } alias LPWSAPROTOCOLCHAIN = WSAPROTOCOLCHAIN*; struct WSAPROTOCOL_INFO { DWORD dwServiceFlags1; DWORD dwServiceFlags2; DWORD dwServiceFlags3; DWORD dwServiceFlags4; DWORD dwProviderFlags; GUID ProviderId; DWORD dwCatalogEntryId; WSAPROTOCOLCHAIN ProtocolChain; int iVersion; int iAddressFamily; int iMaxSockAddr; int iMinSockAddr; int iSocketType; int iProtocol; int iProtocolMaxOffset; int iNetworkByteOrder; int iSecurityScheme; DWORD dwMessageSize; DWORD dwProviderReserved; TCHAR[WSAPROTOCOL_LEN + 1] szProtocol; } alias LPWSAPROTOCOL_INFO = WSAPROTOCOL_INFO*; extern (Windows) @nogc nothrow { private SOCKET WSASocketW(int af, int type, int protocol, LPWSAPROTOCOL_INFO lpProtocolInfo, GROUP g, DWORD dwFlags); int WSARecv(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); int WSASend(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, DWORD lpFlags, LPOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); } alias WSASocket = WSASocketW; alias LPFN_GETACCEPTEXSOCKADDRS = VOID function(PVOID, DWORD, DWORD, DWORD, SOCKADDR**, LPINT, SOCKADDR**, LPINT); const GUID WSAID_GETACCEPTEXSOCKADDRS = {0xb5367df2,0xcbac,0x11cf,[0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92]}; struct WSABUF { ULONG len; CHAR* buf; } alias WSABUF* LPWSABUF; struct WSAOVERLAPPED { ULONG_PTR Internal; ULONG_PTR InternalHigh; union { struct { DWORD Offset; DWORD OffsetHigh; } PVOID Pointer; } HANDLE hEvent; } alias LPWSAOVERLAPPED = WSAOVERLAPPED*; enum SO_UPDATE_ACCEPT_CONTEXT = 0x700B; enum OverlappedSocketEvent { accept = 1, read = 2, write = 3, } class State { /// For internal use by Windows API. align(1) OVERLAPPED overlapped; /// File/socket handle. HANDLE handle; /// For keeping events or event masks. int event; } class SocketState : State { private WSABUF buffer; } /** * Socket returned if a connection has been established. */ class OverlappedConnectedSocket : ConnectedSocket { /** * Create a socket. * * Params: * handle = Socket handle. * af = Address family. */ this(socket_t handle, AddressFamily af) { super(handle, af); } /** * Begins to asynchronously receive data from a connected socket. * * Params: * buffer = Storage location for the received data. * flags = Flags. * overlapped = Unique operation identifier. * * Returns: $(D_KEYWORD true) if the operation could be finished synchronously. * $(D_KEYWORD false) otherwise. * * Throws: $(D_PSYMBOL SocketException) if unable to receive. */ bool beginReceive(ubyte[] buffer, SocketState overlapped, Flags flags = Flags(Flag.none)) @trusted { auto receiveFlags = cast(DWORD) flags; overlapped.handle = cast(HANDLE) handle_; overlapped.event = OverlappedSocketEvent.read; overlapped.buffer.len = cast(uint) buffer.length; overlapped.buffer.buf = cast(char*) buffer.ptr; auto result = WSARecv(handle_, &overlapped.buffer, 1u, NULL, &receiveFlags, &overlapped.overlapped, NULL); if (result == SOCKET_ERROR && !wouldHaveBlocked) { throw defaultAllocator.make!SocketException("Unable to receive"); } return result == 0; } /** * Ends a pending asynchronous read. * * Params * overlapped = Unique operation identifier. * * Returns: Number of bytes received. * * Throws: $(D_PSYMBOL SocketException) if unable to receive. */ int endReceive(SocketState overlapped) @trusted out (count) { assert(count >= 0); } do { DWORD lpNumber; BOOL result = GetOverlappedResult(overlapped.handle, &overlapped.overlapped, &lpNumber, FALSE); if (result == FALSE && !wouldHaveBlocked) { disconnected_ = true; throw defaultAllocator.make!SocketException("Unable to receive"); } if (lpNumber == 0) { disconnected_ = true; } return lpNumber; } /** * Sends data asynchronously to a connected socket. * * Params: * buffer = Data to be sent. * flags = Flags. * overlapped = Unique operation identifier. * * Returns: $(D_KEYWORD true) if the operation could be finished synchronously. * $(D_KEYWORD false) otherwise. * * Throws: $(D_PSYMBOL SocketException) if unable to send. */ bool beginSend(ubyte[] buffer, SocketState overlapped, Flags flags = Flags(Flag.none)) @trusted { overlapped.handle = cast(HANDLE) handle_; overlapped.event = OverlappedSocketEvent.write; overlapped.buffer.len = cast(uint) buffer.length; overlapped.buffer.buf = cast(char*) buffer.ptr; auto result = WSASend(handle_, &overlapped.buffer, 1u, NULL, cast(DWORD) flags, &overlapped.overlapped, NULL); if (result == SOCKET_ERROR && !wouldHaveBlocked) { disconnected_ = true; throw defaultAllocator.make!SocketException("Unable to send"); } return result == 0; } /** * Ends a pending asynchronous send. * * Params * overlapped = Unique operation identifier. * * Returns: Number of bytes sent. * * Throws: $(D_PSYMBOL SocketException) if unable to receive. */ int endSend(SocketState overlapped) @trusted out (count) { assert(count >= 0); } do { DWORD lpNumber; BOOL result = GetOverlappedResult(overlapped.handle, &overlapped.overlapped, &lpNumber, FALSE); if (result == FALSE && !wouldHaveBlocked) { disconnected_ = true; throw defaultAllocator.make!SocketException("Unable to receive"); } return lpNumber; } } class OverlappedStreamSocket : StreamSocket { /// Accept extension function pointer. package LPFN_ACCEPTEX acceptExtension; /** * Create a socket. * * Params: * af = Address family. * * Throws: $(D_PSYMBOL SocketException) on errors. */ this(AddressFamily af) @trusted { super(af); scope (failure) { this.close(); } blocking = false; GUID guidAcceptEx = WSAID_ACCEPTEX; DWORD dwBytes; auto result = WSAIoctl(handle_, SIO_GET_EXTENSION_FUNCTION_POINTER, &guidAcceptEx, guidAcceptEx.sizeof, &acceptExtension, acceptExtension.sizeof, &dwBytes, NULL, NULL); if (!result == SOCKET_ERROR) { throw defaultAllocator.make!SocketException("Unable to retrieve an accept extension function pointer"); } } /** * Begins an asynchronous operation to accept an incoming connection attempt. * * Params: * overlapped = Unique operation identifier. * * Returns: $(D_KEYWORD true) if the operation could be finished synchronously. * $(D_KEYWORD false) otherwise. * * Throws: $(D_PSYMBOL SocketException) on accept errors. */ bool beginAccept(SocketState overlapped) @trusted { auto socket = cast(socket_t) socket(addressFamily, SOCK_STREAM, 0); if (socket == socket_t.init) { throw defaultAllocator.make!SocketException("Unable to create socket"); } scope (failure) { closesocket(socket); } DWORD dwBytes; overlapped.handle = cast(HANDLE) socket; overlapped.event = OverlappedSocketEvent.accept; overlapped.buffer.len = (sockaddr_in.sizeof + 16) * 2; overlapped.buffer.buf = defaultAllocator.makeArray!char(overlapped.buffer.len).ptr; // We don't want to get any data now, but only start to accept the connections BOOL result = acceptExtension(handle_, socket, overlapped.buffer.buf, 0u, sockaddr_in.sizeof + 16, sockaddr_in.sizeof + 16, &dwBytes, &overlapped.overlapped); if (result == FALSE && !wouldHaveBlocked) { throw defaultAllocator.make!SocketException("Unable to accept socket connection"); } return result == TRUE; } /** * Asynchronously accepts an incoming connection attempt and creates a * new socket to handle remote host communication. * * Params: * overlapped = Unique operation identifier. * * Returns: Connected socket. * * Throws: $(D_PSYMBOL SocketException) if unable to accept. */ OverlappedConnectedSocket endAccept(SocketState overlapped) @trusted { scope (exit) { defaultAllocator.dispose(overlapped.buffer.buf[0..overlapped.buffer.len]); } auto socket = defaultAllocator.make!OverlappedConnectedSocket(cast(socket_t) overlapped.handle, addressFamily); scope (failure) { defaultAllocator.dispose(socket); } socket.setOption(SocketOptionLevel.SOCKET, cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT, cast(size_t) handle); return socket; } } } version (linux) { enum SOCK_NONBLOCK = O_NONBLOCK; extern(C) int accept4(int, sockaddr*, socklen_t*, int flags) @nogc nothrow; } private immutable { typeof(&getaddrinfo) getaddrinfoPointer; typeof(&freeaddrinfo) freeaddrinfoPointer; } shared static this() { version (Windows) { auto ws2Lib = GetModuleHandle("ws2_32.dll"); getaddrinfoPointer = cast(typeof(getaddrinfoPointer)) GetProcAddress(ws2Lib, "getaddrinfo"); freeaddrinfoPointer = cast(typeof(freeaddrinfoPointer)) GetProcAddress(ws2Lib, "freeaddrinfo"); } else version (Posix) { getaddrinfoPointer = &getaddrinfo; freeaddrinfoPointer = &freeaddrinfo; } } /** * Error codes for $(D_PSYMBOL Socket). */ enum SocketError : int { /// Unknown error unknown = 0, /// Firewall rules forbid connection. accessDenied = err.EPERM, /// A socket operation was attempted on a non-socket. notSocket = err.EBADF, /// The network is not available. networkDown = err.ECONNABORTED, /// An invalid pointer address was detected by the underlying socket provider. fault = err.EFAULT, /// An invalid argument was supplied to a $(D_PSYMBOL Socket) member. invalidArgument = err.EINVAL, /// The limit on the number of open sockets has been reached. tooManyOpenSockets = err.ENFILE, /// No free buffer space is available for a Socket operation. noBufferSpaceAvailable = err.ENOBUFS, /// The address family is not supported by the protocol family. operationNotSupported = err.EOPNOTSUPP, /// The protocol is not implemented or has not been configured. protocolNotSupported = err.EPROTONOSUPPORT, /// Protocol error. protocolError = err.EPROTOTYPE, /// The connection attempt timed out, or the connected host has failed to respond. timedOut = err.ETIMEDOUT, /// The support for the specified socket type does not exist in this address family. socketNotSupported = err.ESOCKTNOSUPPORT, } /** * $(D_PSYMBOL SocketException) should be thrown only if one of the socket functions * returns -1 or $(D_PSYMBOL SOCKET_ERROR) and sets $(D_PSYMBOL errno), * because $(D_PSYMBOL SocketException) relies on the $(D_PSYMBOL errno) value. */ class SocketException : Exception { immutable SocketError error = SocketError.unknown; /** * Params: * msg = The message for the exception. * file = The file where the exception occurred. * line = The line number where the exception occurred. * next = The previous exception in the chain of exceptions, if any. */ this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) @nogc @safe nothrow { super(msg, file, line, next); foreach (member; EnumMembers!SocketError) { if (member == lastError) { error = member; return; } } if (lastError == err.ENOMEM) { error = SocketError.noBufferSpaceAvailable; } else if (lastError == err.EMFILE) { error = SocketError.tooManyOpenSockets; } else version (linux) { if (lastError == err.ENOSR) { error = SocketError.networkDown; } } else version (Posix) { if (lastError == err.EPROTO) { error = SocketError.networkDown; } } } } /** * Class for creating a network communication endpoint using the Berkeley * sockets interfaces of different types. */ abstract class Socket { version (Posix) { /** * How a socket is shutdown. */ enum Shutdown : int { receive = SHUT_RD, /// Socket receives are disallowed send = SHUT_WR, /// Socket sends are disallowed both = SHUT_RDWR, /// Both receive and send } } else version (Windows) { /// Property to get or set whether the socket is blocking or nonblocking. private bool blocking_ = true; /** * How a socket is shutdown. */ enum Shutdown : int { receive = SD_RECEIVE, /// Socket receives are disallowed. send = SD_SEND, /// Socket sends are disallowed. both = SD_BOTH, /// Both receive and send. } // The WinSock timeouts seem to be effectively skewed by a constant // offset of about half a second (in milliseconds). private enum WINSOCK_TIMEOUT_SKEW = 500; } /// Socket handle. protected socket_t handle_; /// Address family. protected AddressFamily family; private @property void handle(socket_t handle) in { assert(handle != socket_t.init); assert(handle_ == socket_t.init, "Socket handle cannot be changed"); } do { handle_ = handle; // Set the option to disable SIGPIPE on send() if the platform // has it (e.g. on OS X). static if (is(typeof(SO_NOSIGPIPE))) { setOption(SocketOptionLevel.SOCKET, cast(SocketOption)SO_NOSIGPIPE, true); } } @property inout(socket_t) handle() inout const pure nothrow @safe @nogc { return handle_; } /** * Create a socket. * * Params: * handle = Socket. * af = Address family. */ this(socket_t handle, AddressFamily af) in { assert(handle != socket_t.init); } do { scope (failure) { this.close(); } this.handle = handle; family = af; } /** * Closes the socket and calls the destructor on itself. */ ~this() nothrow @trusted @nogc { this.close(); } /** * Get a socket option. * * Params: * level = Protocol level at that the option exists. * option = Option. * result = Buffer to save the result. * * Returns: The number of bytes written to $(D_PARAM result). * * Throws: $(D_PSYMBOL SocketException) on error. */ protected int getOption(SocketOptionLevel level, SocketOption option, void[] result) const @trusted { auto length = cast(socklen_t) result.length; if (getsockopt(handle_, cast(int) level, cast(int) option, result.ptr, &length) == SOCKET_ERROR) { throw defaultAllocator.make!SocketException("Unable to get socket option"); } return length; } /// Ditto. int getOption(SocketOptionLevel level, SocketOption option, out size_t result) const @trusted { return getOption(level, option, (&result)[0..1]); } /// Ditto. int getOption(SocketOptionLevel level, SocketOption option, out Linger result) const @trusted { return getOption(level, option, (&result.clinger)[0..1]); } /// Ditto. int getOption(SocketOptionLevel level, SocketOption option, out Duration result) const @trusted { // WinSock returns the timeout values as a milliseconds DWORD, // while Linux and BSD return a timeval struct. version (Posix) { timeval tv; auto ret = getOption(level, option, (&tv)[0..1]); result = dur!"seconds"(tv.tv_sec) + dur!"usecs"(tv.tv_usec); } else version (Windows) { int msecs; auto ret = getOption(level, option, (&msecs)[0 .. 1]); if (option == SocketOption.RCVTIMEO) { msecs += WINSOCK_TIMEOUT_SKEW; } result = dur!"msecs"(msecs); } return ret; } /** * Set a socket option. * * Params: * level = Protocol level at that the option exists. * option = Option. * value = Option value. * * Throws: $(D_PSYMBOL SocketException) on error. */ protected void setOption(SocketOptionLevel level, SocketOption option, void[] value) const @trusted { if (setsockopt(handle_, cast(int)level, cast(int)option, value.ptr, cast(uint) value.length) == SOCKET_ERROR) { throw defaultAllocator.make!SocketException("Unable to set socket option"); } } /// Ditto. void setOption(SocketOptionLevel level, SocketOption option, size_t value) const @trusted { setOption(level, option, (&value)[0..1]); } /// Ditto. void setOption(SocketOptionLevel level, SocketOption option, Linger value) const @trusted { setOption(level, option, (&value.clinger)[0..1]); } /// Ditto. void setOption(SocketOptionLevel level, SocketOption option, Duration value) const @trusted { version (Posix) { timeval tv; value.split!("seconds", "usecs")(tv.tv_sec, tv.tv_usec); setOption(level, option, (&tv)[0..1]); } else version (Windows) { auto msecs = cast(int) value.total!"msecs"; if (msecs > 0 && option == SocketOption.RCVTIMEO) { msecs = max(1, msecs - WINSOCK_TIMEOUT_SKEW); } setOption(level, option, msecs); } } /** * Returns: Socket's blocking flag. */ @property inout(bool) blocking() inout const nothrow @nogc { version (Posix) { return !(fcntl(handle_, F_GETFL, 0) & O_NONBLOCK); } else version (Windows) { return blocking_; } } /** * Params: * yes = Socket's blocking flag. */ @property void blocking(bool yes) { version (Posix) { int fl = fcntl(handle_, F_GETFL, 0); if (fl != SOCKET_ERROR) { fl = yes ? fl & ~O_NONBLOCK : fl | O_NONBLOCK; fl = fcntl(handle_, F_SETFL, fl); } if (fl == SOCKET_ERROR) { throw defaultAllocator.make!SocketException("Unable to set socket blocking"); } } else version (Windows) { uint num = !yes; if (ioctlsocket(handle_, FIONBIO, &num) == SOCKET_ERROR) { throw defaultAllocator.make!SocketException("Unable to set socket blocking"); } blocking_ = yes; } } /** * Returns: The socket's address family. */ @property AddressFamily addressFamily() const @nogc @safe pure nothrow { return family; } /** * Returns: $(D_KEYWORD true) if this is a valid, alive socket. */ @property bool isAlive() @trusted const nothrow @nogc { int type; socklen_t typesize = cast(socklen_t) type.sizeof; return !getsockopt(handle_, SOL_SOCKET, SO_TYPE, cast(char*)&type, &typesize); } /** * Disables sends and/or receives. * * Params: * how = What to disable. * * See_Also: * $(D_PSYMBOL Shutdown) */ void shutdown(Shutdown how = Shutdown.both) @nogc @trusted const nothrow { .shutdown(handle_, cast(int)how); } /** * Immediately drop any connections and release socket resources. * Calling $(D_PSYMBOL shutdown) before $(D_PSYMBOL close) is recommended * for connection-oriented sockets. The $(D_PSYMBOL Socket) object is no * longer usable after $(D_PSYMBOL close). */ void close() nothrow @trusted @nogc { version(Windows) { .closesocket(handle_); } else version(Posix) { .close(handle_); } handle_ = socket_t.init; } /** * Listen for an incoming connection. $(D_PSYMBOL bind) must be called before you * can $(D_PSYMBOL listen). * * Params: * backlog = Request of how many pending incoming connections are * queued until $(D_PSYMBOL accept)ed. */ void listen(int backlog) const @trusted { if (.listen(handle_, backlog) == SOCKET_ERROR) { throw defaultAllocator.make!SocketException("Unable to listen on socket"); } } /** * Compare handles. * * Params: * that = Another handle. * * Returns: Comparision result. */ int opCmp(size_t that) const pure nothrow @safe @nogc { return handle_ < that ? -1 : handle_ > that ? 1 : 0; } } /** * Interface with common fileds for stream and connected sockets. */ interface ConnectionOrientedSocket { /** * Flags may be OR'ed together. */ enum Flag : int { none = 0, /// No flags specified outOfBand = MSG_OOB, /// Out-of-band stream data peek = MSG_PEEK, /// Peek at incoming data without removing it from the queue, only for receiving dontRoute = MSG_DONTROUTE, /// Data should not be subject to routing; this flag may be ignored. Only for sending } alias Flags = BitFlags!Flag; } class StreamSocket : Socket, ConnectionOrientedSocket { /** * Create a socket. * * Params: * af = Address family. */ this(AddressFamily af) @trusted { auto handle = cast(socket_t) socket(af, SOCK_STREAM, 0); if (handle == socket_t.init) { throw defaultAllocator.make!SocketException("Unable to create socket"); } super(handle, af); } /** * Associate a local address with this socket. * * Params: * address = Local address. * * Throws: $(D_PSYMBOL SocketException) if unable to bind. */ void bind(Address address) @trusted const { if (.bind(handle_, address.name, address.length) == SOCKET_ERROR) { throw defaultAllocator.make!SocketException("Unable to bind socket"); } } /** * Accept an incoming connection. * * The blocking mode is always inherited. * * Returns: $(D_PSYMBOL Socket) for the accepted connection or * $(D_KEYWORD null) if the call would block on a * non-blocking socket. * * Throws: $(D_PSYMBOL SocketException) if unable to accept. */ ConnectedSocket accept() @trusted { socket_t sock; version (linux) { int flags; if (!blocking) { flags |= SOCK_NONBLOCK; } sock = cast(socket_t).accept4(handle_, null, null, flags); } else { sock = cast(socket_t).accept(handle_, null, null); } if (sock == socket_t.init) { if (wouldHaveBlocked()) { return null; } throw defaultAllocator.make!SocketException("Unable to accept socket connection"); } auto newSocket = defaultAllocator.make!ConnectedSocket(sock, addressFamily); version (linux) { // Blocking mode already set } else version (Posix) { if (!blocking) { try { newSocket.blocking = blocking; } catch (SocketException e) { defaultAllocator.dispose(newSocket); throw e; } } } else version (Windows) { // Inherits blocking mode newSocket.blocking_ = blocking; } return newSocket; } } /** * Socket returned if a connection has been established. */ class ConnectedSocket : Socket, ConnectionOrientedSocket { /** * $(D_KEYWORD true) if the stream socket peer has performed an orderly * shutdown. */ protected bool disconnected_; /** * Returns: $(D_KEYWORD true) if the stream socket peer has performed an orderly * shutdown. */ @property inout(bool) disconnected() inout const pure nothrow @safe @nogc { return disconnected_; } /** * Create a socket. * * Params: * handle = Socket. * af = Address family. */ this(socket_t handle, AddressFamily af) { super(handle, af); } version (Windows) { private static int capToMaxBuffer(size_t size) pure nothrow @safe @nogc { // Windows uses int instead of size_t for length arguments. // Luckily, the send/recv functions make no guarantee that // all the data is sent, so we use that to send at most // int.max bytes. return size > size_t (int.max) ? int.max : cast(int) size; } } else { private static size_t capToMaxBuffer(size_t size) pure nothrow @safe @nogc { return size; } } /** * Receive data on the connection. * * Params: * buf = Buffer to save received data. * flags = Flags. * * Returns: The number of bytes received or 0 if nothing received * because the call would block. * * Throws: $(D_PSYMBOL SocketException) if unable to receive. */ ptrdiff_t receive(ubyte[] buf, Flags flags = Flag.none) @trusted { ptrdiff_t ret; if (!buf.length) { return 0; } ret = recv(handle_, buf.ptr, capToMaxBuffer(buf.length), cast(int) flags); if (ret == 0) { disconnected_ = true; } else if (ret == SOCKET_ERROR) { if (wouldHaveBlocked()) { return 0; } disconnected_ = true; throw defaultAllocator.make!SocketException("Unable to receive"); } return ret; } /** * Send data on the connection. If the socket is blocking and there is no * buffer space left, $(D_PSYMBOL send) waits, non-blocking socket returns * 0 in this case. * * Params: * buf = Data to be sent. * flags = Flags. * * Returns: The number of bytes actually sent. * * Throws: $(D_PSYMBOL SocketException) if unable to send. */ ptrdiff_t send(const(ubyte)[] buf, Flags flags = Flag.none) const @trusted { int sendFlags = cast(int) flags; ptrdiff_t sent; static if (is(typeof(MSG_NOSIGNAL))) { sendFlags |= MSG_NOSIGNAL; } sent = .send(handle_, buf.ptr, capToMaxBuffer(buf.length), sendFlags); if (sent != SOCKET_ERROR) { return sent; } else if (wouldHaveBlocked()) { return 0; } throw defaultAllocator.make!SocketException("Unable to send"); } } /** * Socket address representation. */ abstract class Address { /** * Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure. */ abstract @property inout(sockaddr)* name() inout pure nothrow @nogc; /** * Returns: Actual size of underlying $(D_PSYMBOL sockaddr) structure. */ abstract @property inout(socklen_t) length() inout const pure nothrow @nogc; } class InternetAddress : Address { version (Windows) { /// Internal internet address representation. protected SOCKADDR_STORAGE storage; } else version (Posix) { /// Internal internet address representation. protected sockaddr_storage storage; } immutable ushort port_; enum { anyPort = 0, } this(in string host, ushort port = anyPort) { if (getaddrinfoPointer is null || freeaddrinfoPointer is null) { throw defaultAllocator.make!AddressException("Address info lookup is not available on this system"); } addrinfo* ai_res; port_ = port; // Make C-string from host. char[] node = defaultAllocator.makeArray!char(host.length + 1); node[0.. $ - 1] = host; node[$ - 1] = '\0'; scope (exit) { defaultAllocator.dispose(node); } // Convert port to a C-string. char[6] service = [0, 0, 0, 0, 0, 0]; const(char)* servicePointer; if (port) { ushort start; for (ushort j = 10, i = 4; i > 0; j *= 10, --i) { ushort rest = port % 10; if (rest != 0) { service[i] = cast(char) (rest + '0'); start = i; } port /= 10; } servicePointer = service[start..$].ptr; } auto ret = getaddrinfoPointer(node.ptr, servicePointer, null, &ai_res); if (ret) { throw defaultAllocator.make!AddressException("Address info lookup failed"); } scope (exit) { freeaddrinfoPointer(ai_res); } ubyte* dp = cast(ubyte*) &storage, sp = cast(ubyte*) ai_res.ai_addr; for (auto i = ai_res.ai_addrlen; i > 0; --i, dp++, sp++) { *dp = *sp; } if (ai_res.ai_family != AddressFamily.INET && ai_res.ai_family != AddressFamily.INET6) { throw defaultAllocator.make!AddressException("Wrong address family"); } } /** * Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure. */ override @property inout(sockaddr)* name() inout pure nothrow @nogc { return cast(sockaddr*) &storage; } /** * Returns: Actual size of underlying $(D_PSYMBOL sockaddr) structure. */ override @property inout(socklen_t) length() inout const pure nothrow @nogc { // FreeBSD wants to know the exact length of the address on bind. switch (family) { case AddressFamily.INET: return sockaddr_in.sizeof; case AddressFamily.INET6: return sockaddr_in6.sizeof; default: assert(false); } } /** * Returns: Family of this address. */ @property inout(AddressFamily) family() inout const pure nothrow @nogc { return cast(AddressFamily) storage.ss_family; } @property inout(ushort) port() inout const pure nothrow @nogc { return port_; } } /** * Checks if the last error is a serious error or just a special * behaviour error of non-blocking sockets (for example an error * returned because the socket would block or because the * asynchronous operation was successfully started but not finished yet). * * Returns: $(D_KEYWORD false) if a serious error happened, $(D_KEYWORD true) * otherwise. */ bool wouldHaveBlocked() nothrow @trusted @nogc { version (Posix) { return err.errno == err.EAGAIN || err.errno == err.EWOULDBLOCK; } else version (Windows) { return WSAGetLastError() == ERROR_IO_PENDING || WSAGetLastError() == err.EWOULDBLOCK || WSAGetLastError() == ERROR_IO_INCOMPLETE; } } /** * Returns: Platform specific error code. */ private @property int lastError() nothrow @safe @nogc { version (Windows) { return WSAGetLastError(); } else { return err.errno; } } ================================================ FILE: dlib/network/url.d ================================================ /* Copyright (c) 2016-2025 Eugene Wissner Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * URL parser * * Copyright: Eugene Wissner 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Eugene Wissner */ module dlib.network.url; import std.ascii: isAlphaNum, isDigit; import std.traits: isSomeString; import std.uni: isAlpha, isNumber; import std.uri; version (unittest) private { import std.typecons; static Tuple!(string, string[string], ushort)[] URLTests; } static this() { version (unittest) { URLTests = [ tuple(`127.0.0.1`, [ "path": "127.0.0.1", ], ushort(0)), tuple(`http://127.0.0.1`, [ "scheme": "http", "host": "127.0.0.1", ], ushort(0)), tuple(`http://127.0.0.1/`, [ "scheme": "http", "host": "127.0.0.1", "path": "/", ], ushort(0)), tuple(`127.0.0.1/`, [ "path": "127.0.0.1/", ], ushort(0)), tuple(`127.0.0.1:60000/`, [ "host": "127.0.0.1", "path": "/", ], ushort(60000)), tuple(`example.org`, [ "path": "example.org", ], ushort(0)), tuple(`example.org/`, [ "path": "example.org/", ], ushort(0)), tuple(`http://example.org`, [ "scheme": "http", "host": "example.org", ], ushort(0)), tuple(`http://example.org/`, [ "scheme": "http", "host": "example.org", "path": "/", ], ushort(0)), tuple(`www.example.org`, [ "path": "www.example.org", ], ushort(0)), tuple(`www.example.org/`, [ "path": "www.example.org/", ], ushort(0)), tuple(`http://www.example.org`, [ "scheme": "http", "host": "www.example.org", ], ushort(0)), tuple(`http://www.example.org/`, [ "scheme": "http", "host": "www.example.org", "path": "/", ], ushort(0)), tuple(`www.example.org:2`, [ "host": "www.example.org", ], ushort(2)), tuple(`http://www.example.org:80`, [ "scheme": "http", "host": "www.example.org", ], ushort(80)), tuple(`http://www.example.org:80/`, [ "scheme": "http", "host": "www.example.org", "path": "/", ], ushort(80)), tuple(`http://www.example.org/index.html`, [ "scheme": "http", "host": "www.example.org", "path": "/index.html", ], ushort(0)), tuple(`www.example.org/?`, [ "path": "www.example.org/", "query": "", ], ushort(0)), tuple(`www.example.org:80/?`, [ "host": "www.example.org", "path": "/", "query": "", ], ushort(80)), tuple(`http://www.example.org/?`, [ "scheme": "http", "host": "www.example.org", "path": "/", "query": "", ], ushort(0)), tuple(`http://www.example.org:80/?`, [ "scheme": "http", "host": "www.example.org", "path": "/", "query": "", ], ushort(80)), tuple(`http://www.example.org:80/index.html`, [ "scheme": "http", "host": "www.example.org", "path": "/index.html", ], ushort(80)), tuple(`http://www.example.org:80/foo/bar/index.html`, [ "scheme": "http", "host": "www.example.org", "path": "/foo/bar/index.html", ], ushort(80)), tuple(`http://www.example.org:80/this/is/a/very/deep/directory/structure/and/file.png`, [ "scheme": "http", "host": "www.example.org", "path": "/this/is/a/very/deep/directory/structure/and/file.png", ], ushort(80)), tuple(`http://www.example.org:80/deep/directory/structure/and/file.png?lots=1&of=2¶meters=3&too=4`, [ "scheme": "http", "host": "www.example.org", "path": "/deep/directory/structure/and/file.png", "query": "lots=1&of=2¶meters=3&too=4", ], ushort(80)), tuple(`http://www.example.org:80/this/is/a/very/deep/directory/structure/and/`, [ "scheme": "http", "host": "www.example.org", "path": "/this/is/a/very/deep/directory/structure/and/", ], ushort(80)), tuple(`http://www.example.org:80/this/is/a/very/deep/directory/structure/and/file.php`, [ "scheme": "http", "host": "www.example.org", "path": "/this/is/a/very/deep/directory/structure/and/file.php", ], ushort(80)), tuple(`http://www.example.org:80/this/../a/../deep/directory`, [ "scheme": "http", "host": "www.example.org", "path": "/this/../a/../deep/directory", ], ushort(80)), tuple(`http://www.example.org:80/this/../a/../deep/directory/`, [ "scheme": "http", "host": "www.example.org", "path": "/this/../a/../deep/directory/", ], ushort(80)), tuple(`http://www.example.org:80/this/is/a/very/deep/directory/../image.png`, [ "scheme": "http", "host": "www.example.org", "path": "/this/is/a/very/deep/directory/../image.png", ], ushort(80)), tuple(`http://www.example.org:80/index.html`, [ "scheme": "http", "host": "www.example.org", "path": "/index.html", ], ushort(80)), tuple(`http://www.example.org:80/index.html?`, [ "scheme": "http", "host": "www.example.org", "path": "/index.html", "query": "", ], ushort(80)), tuple(`http://www.example.org:80/#foo`, [ "scheme": "http", "host": "www.example.org", "path": "/", "fragment": "foo", ], ushort(80)), tuple(`http://www.example.org:80/?#`, [ "scheme": "http", "host": "www.example.org", "path": "/", "query": "", "fragment": "", ], ushort(80)), tuple(`http://www.example.org:80/?test=1`, [ "scheme": "http", "host": "www.example.org", "path": "/", "query": "test=1", ], ushort(80)), tuple(`http://www.example.org/?test=1&`, [ "scheme": "http", "host": "www.example.org", "path": "/", "query": "test=1&", ], ushort(0)), tuple(`http://www.example.org:80/?&`, [ "scheme": "http", "host": "www.example.org", "path": "/", "query": "&", ], ushort(80)), tuple(`http://www.example.org:80/index.html?test=1&`, [ "scheme": "http", "host": "www.example.org", "path": "/index.html", "query": "test=1&", ], ushort(80)), tuple(`http://www.example.org/index.html?&`, [ "scheme": "http", "host": "www.example.org", "path": "/index.html", "query": "&", ], ushort(0)), tuple(`http://www.example.org:80/index.html?foo&`, [ "scheme": "http", "host": "www.example.org", "path": "/index.html", "query": "foo&", ], ushort(80)), tuple(`http://www.example.org/index.html?&foo`, [ "scheme": "http", "host": "www.example.org", "path": "/index.html", "query": "&foo", ], ushort(0)), tuple(`http://www.example.org:80/index.html?test=1&test2=char`, [ "scheme": "http", "host": "www.example.org", "path": "/index.html", "query": "test=1&test2=char", ], ushort(80)), tuple(`www.example.org:80/index.html?test=1&test2=char#some_ref123`, [ "host": "www.example.org", "path": "/index.html", "query": "test=1&test2=char", "fragment": "some_ref123", ], ushort(80)), tuple(`http://secret@www.example.org:80/index.html?test=1&test2=char#some_ref123`, [ "scheme": "http", "host": "www.example.org", "user": "secret", "path": "/index.html", "query": "test=1&test2=char", "fragment": "some_ref123", ], ushort(80)), tuple(`http://secret:@www.example.org/index.html?test=1&test2=char#some_ref123`, [ "scheme": "http", "host": "www.example.org", "user": "secret", "pass": "", "path": "/index.html", "query": "test=1&test2=char", "fragment": "some_ref123", ], ushort(0)), tuple(`http://:hideout@www.example.org:80/index.html?test=1&test2=char#some_ref123`, [ "scheme": "http", "host": "www.example.org", "user": "", "pass": "hideout", "path": "/index.html", "query": "test=1&test2=char", "fragment": "some_ref123", ], ushort(80)), tuple(`http://secret:hideout@www.example.org/index.html?test=1&test2=char#some_ref123`, [ "scheme": "http", "host": "www.example.org", "user": "secret", "pass": "hideout", "path": "/index.html", "query": "test=1&test2=char", "fragment": "some_ref123", ], ushort(0)), tuple(`http://secret:hid:out@www.example.org:80/index.html?test=1&test2=int#some_ref123`, [ "scheme": "http", "host": "www.example.org", "user": "secret", "pass": "hid:out", "path": "/index.html", "query": "test=1&test2=int", "fragment": "some_ref123", ], ushort(80)), tuple(`nntp://news.example.org`, [ "scheme": "nntp", "host": "news.example.org", ], ushort(0)), tuple(`ftp://ftp.gnu.org/gnu/glic/glibc.tar.gz`, [ "scheme": "ftp", "host": "ftp.gnu.org", "path": "/gnu/glic/glibc.tar.gz", ], ushort(0)), tuple(`zlib:http://foo@bar`, [ "scheme": "zlib", "path": "http://foo@bar", ], ushort(0)), tuple(`zlib:filename.txt`, [ "scheme": "zlib", "path": "filename.txt", ], ushort(0)), tuple(`zlib:/path/to/my/file/file.txt`, [ "scheme": "zlib", "path": "/path/to/my/file/file.txt", ], ushort(0)), tuple(`foo://foo@bar`, [ "scheme": "foo", "host": "bar", "user": "foo", ], ushort(0)), tuple(`mailto:me@mydomain.com`, [ "scheme": "mailto", "path": "me@mydomain.com", ], ushort(0)), tuple(`/foo.php?a=b&c=d`, [ "path": "/foo.php", "query": "a=b&c=d", ], ushort(0)), tuple(`foo.php?a=b&c=d`, [ "path": "foo.php", "query": "a=b&c=d", ], ushort(0)), tuple(`http://user:passwd@www.example.com:8080?bar=1&boom=0`, [ "scheme": "http", "host": "www.example.com", "user": "user", "pass": "passwd", "query": "bar=1&boom=0", ], ushort(8080)), tuple(`file:///path/to/file`, [ "scheme": "file", "path": "/path/to/file", ], ushort(0)), tuple(`file://path/to/file`, [ "scheme": "file", "host": "path", "path": "/to/file", ], ushort(0)), tuple(`file:/path/to/file`, [ "scheme": "file", "path": "/path/to/file", ], ushort(0)), tuple(`http://1.2.3.4:/abc.asp?a=1&b=2`, [ "scheme": "http", "host": "1.2.3.4", "path": "/abc.asp", "query": "a=1&b=2", ], ushort(0)), tuple(`http://foo.com#bar`, [ "scheme": "http", "host": "foo.com", "fragment": "bar", ], ushort(0)), tuple(`scheme:`, [ "scheme": "scheme", ], ushort(0)), tuple(`foo+bar://baz@bang/bla`, [ "scheme": "foo+bar", "host": "bang", "user": "baz", "path": "/bla", ], ushort(0)), tuple(`gg:9130731`, [ "scheme": "gg", "path": "9130731", ], ushort(0)), tuple(`http://10.10.10.10/:80`, [ "scheme": "http", "host": "10.10.10.10", "path": "/:80", ], ushort(0)), tuple(`http://x:?`, [ "scheme": "http", "host": "x", "query": "", ], ushort(0)), tuple(`x:blah.com`, [ "scheme": "x", "path": "blah.com", ], ushort(0)), tuple(`x:/blah.com`, [ "scheme": "x", "path": "/blah.com", ], ushort(0)), tuple(`http://::?`, [ "scheme": "http", "host": ":", "query": "", ], ushort(0)), tuple(`http://::#`, [ "scheme": "http", "host": ":", "fragment": "", ], ushort(0)), tuple(`http://?:/`, [ "scheme": "http", "host": "?", "path": "/", ], ushort(0)), tuple(`http://@?:/`, [ "scheme": "http", "host": "?", "user": "", "path": "/", ], ushort(0)), tuple(`file:///:`, [ "scheme": "file", "path": "/:", ], ushort(0)), tuple(`file:///a:/`, [ "scheme": "file", "path": "a:/", ], ushort(0)), tuple(`file:///ab:/`, [ "scheme": "file", "path": "/ab:/", ], ushort(0)), tuple(`file:///a:/`, [ "scheme": "file", "path": "a:/", ], ushort(0)), tuple(`file:///@:/`, [ "scheme": "file", "path": "@:/", ], ushort(0)), tuple(`file:///:80/`, [ "scheme": "file", "path": "/:80/", ], ushort(0)), tuple(`[]`, [ "path": "[]", ], ushort(0)), tuple(`http://[x:80]/`, [ "scheme": "http", "host": "[x:80]", "path": "/", ], ushort(0)), tuple(``, [ "path": "", ], ushort(0)), tuple(`/`, [ "path": "/", ], ushort(0)), tuple(`/rest/Users?filter={"id":"789"}`, [ "path": "/rest/Users", "query": `filter={"id":"789"}`, ], ushort(0)), tuple(`//example.org`, [ "host": "example.org", ], ushort(0)), tuple(`/standard/?fq=B:20001`, [ "path": "/standard/", "query": "fq=B:20001", ], ushort(0)), tuple(`/standard/?fq=B:200013`, [ "path": "/standard/", "query": "fq=B:200013", ], ushort(0)), tuple(`/standard/?fq=home:012345`, [ "path": "/standard/", "query": "fq=home:012345", ], ushort(0)), tuple(`/standard/?fq=home:01234`, [ "path": "/standard/", "query": "fq=home:01234", ], ushort(0)), tuple(`http://user:pass@host`, [ "scheme": "http", "host": "host", "user": "user", "pass": "pass", ], ushort(0)), tuple(`//user:pass@host`, [ "host": "host", "user": "user", "pass": "pass", ], ushort(0)), tuple(`//user@host`, [ "host": "host", "user": "user", ], ushort(0)), tuple(`//example.org:99/hey?a=b#c=d`, [ "host": "example.org", "path": "/hey", "query": "a=b", "fragment": "c=d", ], ushort(99)), tuple(`//example.org/hey?a=b#c=d`, [ "host": "example.org", "path": "/hey", "query": "a=b", "fragment": "c=d", ], ushort(0)), tuple(`http://example.org/some/path.cgi?t=1#fragment?data`, [ "scheme": "http", "host": "example.org", "path": "/some/path.cgi", "query": "t=1", "fragment": "fragment?data", ], ushort(0)), tuple(`http://example.org/some/path.cgi#fragment?data`, [ "scheme": "http", "host": "example.org", "path": "/some/path.cgi", "fragment": "fragment?data", ], ushort(0)), tuple(`x://::abc/?`, string[string].init, ushort(0)), tuple(`http:///blah.com`, string[string].init, ushort(0)), tuple(`http://:80`, string[string].init, ushort(0)), tuple(`http://user@:80`, string[string].init, ushort(0)), tuple(`http://user:pass@:80`, string[string].init, ushort(0)), tuple(`http://:`, string[string].init, ushort(0)), tuple(`http://@/`, string[string].init, ushort(0)), tuple(`http://@:/`, string[string].init, ushort(0)), tuple(`http://:/`, string[string].init, ushort(0)), tuple(`http://?`, string[string].init, ushort(0)), tuple(`http://#`, string[string].init, ushort(0)), tuple(`http://:?`, string[string].init, ushort(0)), tuple(`http://blah.com:123456`, string[string].init, ushort(0)), tuple(`http://blah.com:70000`, string[string].init, ushort(0)), tuple(`http://blah.com:abcdef`, string[string].init, ushort(0)), tuple(`http://secret@hideout@www.example.org:80/index.html?test=1&test2=char#some_ref123`, string[string].init, ushort(0)), tuple(`http://user:@pass@host/path?argument?value#etc`, string[string].init, ushort(0)), tuple(`http://foo.com\@bar.com`, string[string].init, ushort(0)), tuple(`http://email@address.com:pass@example.org`, string[string].init, ushort(0)), tuple(`:`, string[string].init, ushort(0)), ]; } } /** * A Unique Resource Locator. * * Params: * U = URL string type. */ struct URL(U = string) if (isSomeString!U) { /** The URL scheme. */ U scheme; /** The username. */ U user; /** The password. */ U pass; /** The hostname. */ U host; /** The port number. */ ushort port; /** The path. */ U path; /** The query string. */ U query; /** The anchor. */ U fragment; /** * Attempts to parse an URL from a string. * Output string data (scheme, user, etc.) are just slices of input string (e.g., no memory allocation and copying). * * Params: * source = The string containing the URL. * * Throws: $(D_PSYMBOL URIException) if the URL is malformed. */ this(U source) { auto value = source; ptrdiff_t pos = -1, endPos = value.length, start; foreach (i, ref c; source) { if (pos == -1 && c == ':') { pos = i; } if (endPos == value.length && (c == '?' || c == '#')) { endPos = i; } } // Check if the colon is a part of the scheme or the port and parse // the appropriate part if (value.length > 1 && value[0] == '/' && value[1] == '/') { // Relative scheme start = 2; } else if (pos > 0) { // Validate scheme // [ toLower(alpha) | digit | "+" | "-" | "." ] foreach (ref c; value[0..pos]) { if (!c.isAlphaNum && c != '+' && c != '-' && c != '.') { if (endPos > pos) { if (!parsePort(value[pos..$])) { throw new URIException("Failed to parse port"); } } goto ParsePath; } } if (value.length == pos + 1) // only scheme is available { scheme = value[0 .. $ - 1]; return; } else if (value.length > pos + 1 && value[pos + 1] == '/') { scheme = value[0..pos]; if (value.length > pos + 2 && value[pos + 2] == '/') { start = pos + 3; if (scheme == "file" && value.length > start && value[start] == '/') { // Windows drive letters if (value.length - start > 2 && value[start + 2] == ':') { ++start; } goto ParsePath; } } else { start = pos + 1; goto ParsePath; } } else // certain schemas like mailto: and zlib: may not have any / after them { if (!parsePort(value[pos..$])) { scheme = value[0..pos]; start = pos + 1; goto ParsePath; } } } else if (pos == 0 && parsePort(value[pos..$])) { // An URL shouldn't begin with a port number throw new URIException("URL begins with port"); } else { goto ParsePath; } // Parse host pos = -1; for (ptrdiff_t i = start; i < value.length; ++i) { if (value[i] == '@') { pos = i; } else if (value[i] == '/') { endPos = i; break; } } // Check for login and password if (pos != -1) { // *( unreserved / pct-encoded / sub-delims / ":" ) foreach (i, c; value[start..pos]) { if (c == ':') { if (user is null) { user = value[start .. start + i]; pass = value[start + i + 1 .. pos]; } } else if (!c.isAlpha && !c.isNumber && c != '!' && c != ';' && c != '=' && c != '_' && c != '~' && !(c >= '$' && c <= '.')) { if (scheme !is null) { scheme = null; } if (user !is null) { user = null; } if (pass !is null) { pass = null; } throw new URIException("Restricted characters in user information"); } } if (user is null) { user = value[start..pos]; } start = ++pos; } pos = endPos; if (endPos <= 1 || value[start] != '[' || value[endPos - 1] != ']') { // Short circuit portscan // IPv6 embedded address for (ptrdiff_t i = endPos - 1; i >= start; --i) { if (value[i] == ':') { pos = i; if (port == 0 && !parsePort(value[i..endPos])) { if (scheme !is null) { scheme = null; } if (user !is null) { user = null; } if (pass !is null) { pass = null; } throw new URIException("Invalid port"); } break; } } } // Check if we have a valid host, if we don't reject the string as url if (pos <= start) { if (scheme !is null) { scheme = null; } if (user !is null) { user = null; } if (pass !is null) { pass = null; } throw new URIException("Invalid host"); } host = value[start..pos]; if (endPos == value.length) { return; } start = endPos; ParsePath: endPos = value.length; pos = -1; foreach (i, ref c; value[start..$]) { if (c == '?' && pos == -1) { pos = start + i; } else if (c == '#') { endPos = start + i; break; } } if (pos == -1) { pos = endPos; } if (pos > start) { path = value[start..pos]; } if (endPos >= ++pos) { query = value[pos..endPos]; } if (++endPos <= value.length) { fragment = value[endPos..$]; } } ~this() { if (scheme !is null) { scheme = null; } if (user !is null) { user = null; } if (pass !is null) { pass = null; } if (host !is null) { host = null; } if (path !is null) { path = null; } if (query !is null) { query = null; } if (fragment !is null) { fragment = null; } } /** * Attempts to parse and set the port. * * Params: * port = String beginning with a colon followed by the port number and * an optional path (query string and/or fragment), like: * `:12345/some_path` or `:12345`. * * Returns: Whether the port could found. */ private bool parsePort(U port) pure nothrow @safe @nogc { ptrdiff_t i = 1; float lPort = 0; for (; i < port.length && port[i].isDigit() && i <= 6; ++i) { lPort += (port[i] - '0') / cast(float)(10 ^^ (i - 1)); } if (i == 1 && (i == port.length || port[i] == '/')) { return true; } else if (i == port.length || port[i] == '/') { lPort *= 10 ^^ (i - 2); if (lPort > ushort.max) { return false; } this.port = cast(ushort)lPort; return true; } return false; } } unittest { auto u = URL!()("example.org"); assert(u.path == "example.org"); u = URL!()("relative/path"); assert(u.path == "relative/path"); // Host and scheme u = URL!()("https://example.org"); assert(u.scheme == "https"); assert(u.host == "example.org"); assert(u.path is null); assert(u.port == 0); assert(u.fragment is null); // With user and port and path u = URL!()("https://hilary:putnam@example.org:443/foo/bar"); assert(u.scheme == "https"); assert(u.host == "example.org"); assert(u.path == "/foo/bar"); assert(u.port == 443); assert(u.user == "hilary"); assert(u.pass == "putnam"); assert(u.fragment is null); // With query string u = URL!()("https://example.org/?login=true"); assert(u.scheme == "https"); assert(u.host == "example.org"); assert(u.path == "/"); assert(u.query == "login=true"); assert(u.fragment is null); // With query string and fragment u = URL!()("https://example.org/?login=false#label"); assert(u.scheme == "https"); assert(u.host == "example.org"); assert(u.path == "/"); assert(u.query == "login=false"); assert(u.fragment == "label"); u = URL!()("redis://root:password@localhost:2201/path?query=value#fragment"); assert(u.scheme == "redis"); assert(u.user == "root"); assert(u.pass == "password"); assert(u.host == "localhost"); assert(u.port == 2201); assert(u.path == "/path"); assert(u.query == "query=value"); assert(u.fragment == "fragment"); } private unittest { foreach(t; URLTests) { if (t[1].length == 0 && t[2] == 0) { try { URL!()(t[0]); assert(0); } catch (URIException e) { assert(1); } } else { auto u = URL!()(t[0]); assert("scheme" in t[1] ? u.scheme == t[1]["scheme"] : u.scheme is null, t[0]); assert("user" in t[1] ? u.user == t[1]["user"] : u.user is null, t[0]); assert("pass" in t[1] ? u.pass == t[1]["pass"] : u.pass is null, t[0]); assert("host" in t[1] ? u.host == t[1]["host"] : u.host is null, t[0]); assert(u.port == t[2], t[0]); assert("path" in t[1] ? u.path == t[1]["path"] : u.path is null, t[0]); assert("query" in t[1] ? u.query == t[1]["query"] : u.query is null, t[0]); if ("fragment" in t[1]) { assert(u.fragment == t[1]["fragment"], t[0]); } else { assert(u.fragment is null, t[0]); } } } } /** * Contains possible URL components that can be returned from * $(D_PSYMBOL parseURL). */ enum Component : string { scheme = "scheme", host = "host", port = "port", user = "user", pass = "pass", path = "path", query = "query", fragment = "fragment", } /** * Attempts to parse an URL from a string. * * Params: * T = $(D_SYMBOL Component) member or $(D_KEYWORD null) for a * struct with all components. * U = URL string type. * source = The string containing the URL. * * Returns: Requested URL component(s). */ U parseURL(string T, U)(in U source) if ((T == "scheme" || T =="host" || T == "user" || T == "pass" || T == "path" || T == "query" || T == "fragment") && isSomeString!U) { auto ret = URL!U(source); return mixin("ret." ~ T); } /** ditto */ auto parseURL(U)(in U source) if (isSomeString!U) { return URL!U(source); } /** ditto */ ushort parseURL(string T, U)(in U source) if (T == "port" && isSomeString!U) { auto ret = URL!U(source); return ret.port; } unittest { assert(parseURL!(Component.port)("http://example.org:5326") == 5326); immutable dstring url = "http://example.org:5326"; static assert(is(typeof(parseURL(url)) == URL!dstring)); } private unittest { foreach(t; URLTests) { if (t[1].length == 0 && t[2] == 0) { try { parseURL!(Component.port)(t[0]); parseURL!(Component.user)(t[0]); parseURL!(Component.pass)(t[0]); parseURL!(Component.host)(t[0]); parseURL!(Component.path)(t[0]); parseURL!(Component.query)(t[0]); parseURL!(Component.fragment)(t[0]); assert(0); } catch (URIException e) { assert(1); } } else { ushort port = parseURL!(Component.port)(t[0]); string component = parseURL!(Component.scheme)(t[0]); assert("scheme" in t[1] ? component == t[1]["scheme"] : component is null, t[0]); component = parseURL!(Component.user)(t[0]); assert("user" in t[1] ? component == t[1]["user"] : component is null, t[0]); component = parseURL!(Component.pass)(t[0]); assert("pass" in t[1] ? component == t[1]["pass"] : component is null, t[0]); component = parseURL!(Component.host)(t[0]); assert("host" in t[1] ? component == t[1]["host"] : component is null, t[0]); assert(port == t[2], t[0]); component = parseURL!(Component.path)(t[0]); assert("path" in t[1] ? component == t[1]["path"] : component is null, t[0]); component = parseURL!(Component.query)(t[0]); assert("query" in t[1] ? component == t[1]["query"] : component is null, t[0]); component = parseURL!(Component.fragment)(t[0]); if ("fragment" in t[1]) { assert(component == t[1]["fragment"], t[0]); } else { assert(component is null, t[0]); } } } } ================================================ FILE: dlib/package.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * dlib - general purpose library * * Description: * dlib is a high-level general purpose library for * $(LINK2 https://dlang.org, D language) intended to * game engine developers. It provides basic building blocks for writing * graphics-intensive applications: containers, data streams, linear algebra * and image decoders. * * dlib has no external dependencies aside D's standard library. dlib is created * and maintained by $(LINK2 https://github.com/gecko0307, Timur Gafarov). * * If you like dlib, please support its development on * $(LINK2 https://www.patreon.com/gecko0307, Patreon) or * $(LINK2 https://liberapay.com/gecko0307, Liberapay). You can also make one-time * donation via $(LINK2 https://www.paypal.me/tgafarov, PayPal) or * $(LINK2 https://nowpayments.io/donation/gecko0307, NOWPayments). * * If you want to use dlib on macOS then, please, first read the * $(LINK2 https://github.com/gecko0307/dlib/wiki/Why-doesn't-dlib-support-macOS, manifesto). * * Currently dlib consists of the following packages: * * - $(LINK2 dlib/core.html, dlib.core) - basic functionality used by other modules (memory management, streams, threads, etc.) * * - $(LINK2 dlib/container.html, dlib.container) - generic data structures (GC-free dynamic and associative arrays and more) * * - $(LINK2 dlib/filesystem.html, dlib.filesystem) - abstract FS interface and its implementations for Windows and POSIX filesystems * * - $(LINK2 dlib/math.html, dlib.math) - linear algebra and numerical analysis (vectors, matrices, quaternions, linear system solvers, interpolation functions, etc.) * * - $(LINK2 dlib/geometry.html, dlib.geometry) - computational geometry (ray casting, primitives, intersection, etc.) * * - $(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) * * - $(LINK2 dlib/audio.html, dlib.audio) - sound processing (8 and 16 bits per sample, synthesizers, WAV export and import) * * - $(LINK2 dlib/network.html, dlib.network) - networking and web functionality * * - $(LINK2 dlib/memory.html, dlib.memory) - memory allocators * * - $(LINK2 dlib/text.html, dlib.text) - text processing, GC-free strings, Unicode decoding and encoding * * - $(LINK2 dlib/random.html, dlib.random) - random number generation * * - $(LINK2 dlib/serialization.html, dlib.serialization) - data serialization (XML and JSON parsers) * * - $(LINK2 dlib/coding.html, dlib.coding)- various data compression and coding algorithms * * - $(LINK2 dlib/concurrency.html, dlib.concurrency) - a thread pool. * * Copyright: Timur Gafarov 2011-2025. * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib; public { import dlib.audio; import dlib.coding; import dlib.concurrency; import dlib.container; import dlib.core; import dlib.filesystem; import dlib.geometry; import dlib.image; import dlib.math; import dlib.memory; import dlib.network; import dlib.random; import dlib.serialization; import dlib.text; } ================================================ FILE: dlib/random/package.d ================================================ /* Copyright (c) 2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ module dlib.random; public { import dlib.random.random; } ================================================ FILE: dlib/random/random.d ================================================ /* Copyright (c) 2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Pseudo-random numbers based on C rand function * * Copyright: Timur Gafarov 2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.random.random; import core.stdc.stdlib; import core.stdc.time; import core.thread.osthread: getpid; import std.math; import std.algorithm: sum; static this() { srand(cast(uint)seed()); } auto seed() { return mix(clock(), time(null), getpid()); } /** * Bob Jenkins' 96 bit mix function */ ulong mix(ulong a, ulong b, ulong c) { a=a-b; a=a-c; a=a^(c >> 13); b=b-c; b=b-a; b=b^(a << 8); c=c-a; c=c-b; c=c^(b >> 13); a=a-b; a=a-c; a=a^(c >> 12); b=b-c; b=b-a; b=b^(a << 16); c=c-a; c=c-b; c=c^(b >> 5); a=a-b; a=a-c; a=a^(c >> 3); b=b-c; b=b-a; b=b^(a << 10); c=c-a; c=c-b; c=c^(b >> 15); return c; } /** * Returns pseudo-random integer between mi (inclusive) and ma (exclusive) */ int randomInRange(int mi, int ma) { return (rand() % (ma - mi)) + mi; } /** * Returns pseudo-random floating-point number in 0..1 range */ T random(T)() { T res = (rand() % RAND_MAX) / cast(T)RAND_MAX; return res; } ================================================ FILE: dlib/serialization/json.d ================================================ /* Copyright (c) 2018-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * JSON parser * * Copyright: Timur Gafarov 2018-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.serialization.json; import std.stdio; import std.conv; import std.string; import std.ascii; import dlib.core.memory; import dlib.core.compound; import dlib.container.array; import dlib.container.dict; import dlib.text.utils; import dlib.text.utf8; import dlib.text.lexer; import dlib.text.str; class JSONLexer { string text; Lexer lexer; string currentLexeme; enum delimiters = [ "{", "}", "[", "]", ",", ":", "\n", " ", "\"", "\'", "`" ]; this(string text) { this.text = text; lexer = New!Lexer(this.text, delimiters); lexer.ignoreNewlines = true; nextLexeme(); } ~this() { Delete(lexer); } void nextLexeme() { // Skip whitespaces string lex; do { lex = lexer.getLexeme(); if (lex.length == 0) break; } while (lex == " " || lex == "\t" || lex == "\n"); // EOF if (lex.length == 0) { currentLexeme = ""; return; } if (lex == "\"" || lex == "\'" || lex == "`") { string quote = lex; size_t startPos = lexer.position() - 1; while (true) { auto nextLex = lexer.getLexeme(); if (nextLex.length == 0) { // EOF without closing quote currentLexeme = text[startPos..$]; return; } else if (nextLex.length == 1 && nextLex == quote) { // Closing quote found size_t endPos = lexer.position(); currentLexeme = text[startPos..endPos]; return; } } } else { currentLexeme = lex; } } } /// JSON types enum enum JSONType { Null, Number, String, Array, Object, Boolean } /// JSON array alias JSONArray = Array!JSONValue; /// JSON object alias JSONObject = Dict!(JSONValue, string); /// JSON value class JSONValue { JSONType type; double asNumber; string asString; JSONArray asArray; JSONObject asObject; bool asBoolean; this() { asNumber = 0.0; asString = ""; asObject = null; asBoolean = false; } void addArrayElement(JSONValue element) { type = JSONType.Array; asArray.append(element); } void addField(string name, JSONValue element) { if (asObject is null) asObject = New!JSONObject(); type = JSONType.Object; asObject[name] = element; } ~this() { if (asArray.length) { foreach(i, e; asArray.data) Delete(e); asArray.free(); } if (asObject) { foreach(name, e; asObject) Delete(e); Delete(asObject); } } } /// JSON parsing result alias JSONResult = Compound!(bool, string); /// JSON parsing errors enum enum JSONError { EOI = JSONResult(false, "unexpected end of input") } /// JSON document class JSONDocument { public: bool isValid; JSONValue root; this(string input) { root = New!JSONValue(); root.type = JSONType.Object; lexer = New!JSONLexer(input); JSONResult res = parseValue(root); isValid = res[0]; if (!isValid) writeln(res[1]); } ~this() { Delete(root); Delete(lexer); } protected: JSONLexer lexer; string currentLexeme() @property { return lexer.currentLexeme; } void nextLexeme() { lexer.nextLexeme(); } JSONResult parseValue(JSONValue value) { if (!currentLexeme.length) return JSONError.EOI; if (currentLexeme == "{") { nextLexeme(); while (currentLexeme.length && currentLexeme != "}") { string identifier = currentLexeme; if (!identifier.length) return JSONError.EOI; if (identifier[0] != '\"' || identifier[$-1] != '\"') return JSONResult(false, format("illegal identifier \"%s\"", identifier)); identifier = identifier[1..$-1]; nextLexeme(); if (currentLexeme != ":") return JSONResult(false, format("\":\" expected, got \"%s\"", currentLexeme)); nextLexeme(); JSONValue newValue = New!JSONValue(); JSONResult res = parseValue(newValue); if (!res[0]) return res; value.addField(identifier, newValue); nextLexeme(); if (currentLexeme == ",") nextLexeme(); else if (currentLexeme != "}") return JSONResult(false, format("\"}\" expected, got \"%s\"", currentLexeme)); } } else if (currentLexeme == "[") { nextLexeme(); while (currentLexeme.length && currentLexeme != "]") { JSONValue newValue = New!JSONValue(); JSONResult res = parseValue(newValue); if (!res[0]) return res; value.addArrayElement(newValue); nextLexeme(); if (currentLexeme == ",") nextLexeme(); else if (currentLexeme != "]") return JSONResult(false, format("\"}\" expected, got \"%s\"", currentLexeme)); } } else { string data = currentLexeme; if (data[0] == '\"') { if (data[$-1] != '\"') return JSONResult(false, format("illegal string \"%s\"", data)); data = data[1..$-1]; value.type = JSONType.String; value.asString = data; } else if (data == "true" || data == "false") { value.type = JSONType.Boolean; value.asBoolean = data.to!bool; } else { value.type = JSONType.Number; value.asNumber = data.to!double; } } return JSONResult(true, ""); } } // unittest { string input = " { \"foo\": \"bar\", \"test\": 100, \"bool\": true, \"arr\": [0, 1, 2] } "; JSONDocument doc = New!JSONDocument(input); assert(doc.root.asObject["foo"].asString == "bar"); assert(doc.root.asObject["test"].asNumber == 100); assert(doc.root.asObject["bool"].asBoolean == true); assert(doc.root.asObject["arr"].asArray[1].asNumber == 1); Delete(doc); } ================================================ FILE: dlib/serialization/package.d ================================================ /* Copyright (c) 2017-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Data serialization * * Copyright: Timur Gafarov 2017-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.serialization; public { import dlib.serialization.json; import dlib.serialization.xml; } ================================================ FILE: dlib/serialization/xml.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * GC-free parser for a subset of XML. * * Description: * Has the following limitations: * * - supports only ASCII and UTF-8 encodings * * - doesn't support DOCTYPE and some other special tags * * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.serialization.xml; import std.stdio; import std.conv; import dlib.core.memory; import dlib.core.compound; import dlib.container.array; import dlib.container.dict; import dlib.container.stack; import dlib.text.lexer; import dlib.text.utils; string[] xmlDelims = [ "<", ">", "", "=", "", "\"", "", "", "\"", "'", " ", "\n", ]; enum XmlToken { TagOpen, TagClose, TagName, Assignment, Quote, PropValue } string emptyStr; string appendChar(string s, dchar ch) { char[7] firstByteMark = [0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC]; char[4] chars; uint byteMask = 0xBF; uint byteMark = 0x80; uint bytesToWrite = 0; if (ch < 0x80) bytesToWrite = 1; else if (ch < 0x800) bytesToWrite = 2; else if (ch < 0x10000) bytesToWrite = 3; else bytesToWrite = 4; char* target = chars.ptr; target += bytesToWrite; switch (bytesToWrite) { case 4: *--target = cast(char)((ch | byteMark) & byteMask); ch >>= 6; goto case 3; case 3: *--target = cast(char)((ch | byteMark) & byteMask); ch >>= 6; goto case 2; case 2: *--target = cast(char)((ch | byteMark) & byteMask); ch >>= 6; goto case 1; case 1: *--target = cast(char)(ch | firstByteMark[bytesToWrite]); break; default: break; } return catStr(s, cast(string)chars[0..bytesToWrite]); } class XmlNode { XmlNode parent; Array!XmlNode children; string name; string text; Dict!(string, string) properties; this(string name, XmlNode parent = null) { this.name = name; this.parent = parent; if (parent !is null) { parent.addChild(this); } this.properties = New!(Dict!(string, string)); } ~this() { if (text.length) Delete(text); if (name.length) Delete(name); foreach(k, v; properties) { Delete(k); Delete(v); } Delete(properties); foreach(c; children) { Delete(c); } children.free(); } XmlNode firstChildByTag(string tag) { XmlNode res = null; foreach(c; children) { if (c.name == tag) { res = c; break; } } return res; } void addChild(XmlNode node) { children.append(node); } void appendText(dchar c) { string newText = appendChar(text, c); if (text.length) Delete(text); text = newText; } string getTextUnmanaged() { Array!char res; res.append(text); foreach(n; children) { string t = n.getTextUnmanaged(); if (t.length) { res.append(t); Delete(t); } } string output = immutableCopy(cast(string)res.data); res.free(); return output; } void printProperties(dstring indent = "") { if (properties.length) { foreach(k, v; properties) writeln(indent, k, " = ", v); } } // Warning! Causes GC allocation! void print(dstring indent = "") { printProperties(indent); foreach(n; children) { auto nm = n.name; if (nm.length) writeln(indent, "tag: ", nm); else writeln(indent, "tag: "); string txt = n.getTextUnmanaged(); if (txt.length) { writeln(indent, "text: ", txt); Delete(txt); } n.print(indent ~ " "); } } } string prop(XmlNode node, string name) { if (name in node.properties) return node.properties[name]; else return ""; } class XmlDocument { XmlNode prolog = null; XmlNode root; this() { root = New!XmlNode(emptyStr); } ~this() { Delete(root); if (prolog) Delete(prolog); } } XmlDocument parseXMLUnmanaged(string text) { XmlDocument doc = New!XmlDocument(); Lexer lex = New!Lexer(text, xmlDelims); Stack!XmlNode nodeStack; nodeStack.push(doc.root); XmlToken expect = XmlToken.TagOpen; bool tagOpening = false; bool xmlPrologDeclaration = false; bool comment = false; bool cdata = false; bool lastCharWasWhitespace = false; string tmpPropName; Array!char tmpPropValue; bool finished = false; bool failed = false; void error(string text, string t) { writefln("XML parse error: %s \"%s\"", text, t); failed = true; } string token; while(!finished) { token = lex.getLexeme(); //writeln(token); if (!token.length) break; //version(None) switch(token) { case "": if (comment) break; if (cdata) cdata = false; else { error("Unexpected token ", token); finished = true; } break; case "": if (cdata) { XmlNode node = New!XmlNode(emptyStr, nodeStack.top); node.text = immutableCopy(token); } else if (comment) comment = false; else { error("Unexpected token ", token); finished = true; } break; case "<": if (comment) break; if (cdata) { XmlNode node = New!XmlNode(emptyStr, nodeStack.top); node.text = immutableCopy(token); } else if (expect == XmlToken.TagOpen) { expect = XmlToken.TagName; tagOpening = true; } else { error("Unexpected token ", token); finished = true; } break; case ">": if (comment) break; if (cdata) { XmlNode node = New!XmlNode(emptyStr, nodeStack.top); node.text = immutableCopy(token); } else if (expect == XmlToken.TagClose && !xmlPrologDeclaration) { expect = XmlToken.TagOpen; } else { error("Unexpected token ", token); finished = true; } break; case "": if (comment) break; if (cdata) { XmlNode node = New!XmlNode(emptyStr, nodeStack.top); node.text = immutableCopy(token); } else if (expect == XmlToken.TagClose && !xmlPrologDeclaration) { expect = XmlToken.TagOpen; nodeStack.pop(); } else { error("Unexpected token ", token); finished = true; } break; case "": if (comment) break; if (cdata) { XmlNode node = New!XmlNode(emptyStr, nodeStack.top); node.text = immutableCopy(token); } else if (expect == XmlToken.TagClose && xmlPrologDeclaration) { expect = XmlToken.TagOpen; xmlPrologDeclaration = false; nodeStack.pop(); } break; case "=": if (comment) break; if (cdata) { XmlNode node = New!XmlNode(emptyStr, nodeStack.top); node.text = immutableCopy(token); } else if (expect == XmlToken.Assignment) { expect = XmlToken.Quote; } else if (expect == XmlToken.TagOpen) { XmlNode node = New!XmlNode(emptyStr, nodeStack.top); node.text = immutableCopy(token); } else { error("Unexpected token ", token); finished = true; } break; case "\"": if (comment) break; if (cdata) { XmlNode node = New!XmlNode(emptyStr, nodeStack.top); node.text = immutableCopy(token); } else if (expect == XmlToken.Quote) { expect = XmlToken.PropValue; } else if (expect == XmlToken.PropValue) { expect = XmlToken.TagClose; nodeStack.top.properties[immutableCopy(tmpPropName)] = immutableCopy(cast(string)tmpPropValue.data); tmpPropValue.free(); } else { error("Unexpected token ", token); finished = true; } break; default: if (comment) break; if (cdata) { XmlNode node = New!XmlNode(emptyStr, nodeStack.top); node.text = immutableCopy(token); break; } if (token != " " && token != "\n") lastCharWasWhitespace = false; if (token == " " || token == "\n") { if (expect == XmlToken.TagOpen) { if (nodeStack.top.children.length) { if (nodeStack.top.children.data[$-1].text == " ") break; } else if (!nodeStack.top.text.length) break; else if (nodeStack.top.text[$-1] == ' ') break; XmlNode node = New!XmlNode(emptyStr, nodeStack.top); node.text = immutableCopy(" "); } else if (expect == XmlToken.PropValue) { if (!lastCharWasWhitespace) { tmpPropValue.append(' '); lastCharWasWhitespace = true; } } } else if (expect == XmlToken.TagName) { expect = XmlToken.TagClose; if (xmlPrologDeclaration) { if (tagOpening) { if (doc.prolog is null) { if (token == "xml") { doc.prolog = New!XmlNode(immutableCopy(token)); nodeStack.push(doc.prolog); tagOpening = false; } else { error("Illegal XML prolog", emptyStr); finished = true; } } else { error("More than one XML prolog is not allowed", emptyStr); finished = true; } } else { nodeStack.pop(); } } else if (tagOpening) { XmlNode node = New!XmlNode(immutableCopy(token), nodeStack.top); nodeStack.push(node); tagOpening = false; } else { if (token == nodeStack.top.name) nodeStack.pop(); else { error("Mismatched tag", emptyStr); finished = true; } } } else if (expect == XmlToken.TagOpen) { XmlNode node = New!XmlNode(emptyStr, nodeStack.top); if (token[0] == '&') { if (token[1] == '#' && token.length > 2) { dchar c = '?'; if (token[2] == 'x') { int code = hexCharacterCode(token[3..$]); if (code == -1) { error("Failed to parse character reference ", token); finished = true; } else c = cast(dchar)code; } else c = cast(dchar)to!uint(token[2..$-1]); node.appendText(c); } else node.text = immutableCopy(token); } else node.text = immutableCopy(token); } else if (expect == XmlToken.TagClose) { expect = XmlToken.Assignment; if (tmpPropName.length) Delete(tmpPropName); tmpPropName = immutableCopy(token); } else if (expect == XmlToken.PropValue) { tmpPropValue.append(token); } else { error("Unexpected token ", token); finished = true; } break; } } if (tmpPropName.length) Delete(tmpPropName); tmpPropValue.free(); nodeStack.free(); Delete(lex); if (failed) { Delete(doc); doc = null; } return doc; } /// unittest { string xml = " é "; XmlDocument doc = parseXMLUnmanaged(xml); assert(doc.prolog.properties["version"] == "1.0"); assert(doc.prolog.properties["encoding"] == "UTF-8"); auto obj = doc.root.children[0]; assert(obj.name == "object"); auto p = obj.children[0]; assert(p.name == "property"); assert(p.properties["name"] == "position"); assert(p.properties["value"] == "0 10 5"); assert(prop(p, "name") == "position"); assert(prop(p, "something") == ""); auto foo = obj.firstChildByTag("foo"); assert(foo.name == "foo"); string fooText = foo.getTextUnmanaged(); assert(fooText == "é"); Delete(fooText); Delete(doc); } int hexCharacterCode(string input) { int res; foreach(c; input) { switch(c) { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': res = res * 0x10 | c - '0'; break; case 'a', 'b', 'c', 'd', 'e', 'f': res = res * 0x10 | c - 'a' + 0xA; break; case 'A', 'B', 'C', 'D', 'E', 'F': res = res * 0x10 | c - 'A' + 0xA; break; case ';': return res; default: return -1; } } return res; } /// unittest { assert(hexCharacterCode("ff00ff") == 16711935); assert(hexCharacterCode("ABABAB;") == 11250603); } ================================================ FILE: dlib/text/common.d ================================================ /* Copyright (c) 2018-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ module dlib.text.common; /// Denotes an end of data enum DECODE_END = -1; /// Denotes an error while decoding enum DECODE_ERROR = -2; ================================================ FILE: dlib/text/encodings.d ================================================ /* Copyright (c) 2018-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Generic encoding tools * * Description: * This module works with any encoder and decoder structs that implement * the following basic interfaces: * --- * struct Encoder * { * // Encodes a Unicode code point to user-provided buffer. * // Should return bytes written or 0 at error * size_t encode(uint codePoint, char[] buffer) * } * * struct Decoder * { * // An input range that iterates characters of a string, * // decoding them to Unicode code points * auto decode(string input) * } * --- * * Copyright: Timur Gafarov 2018-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.text.encodings; import dlib.container.array; import dlib.text.utils; public { import dlib.text.utf8; import dlib.text.utf16; } /** * Converts a string from one encoding to another. * Decoder and encoder are specified at compile time * * Examples: * --- * string s = transcode!(UTF16Decoder, UTF8Encoder)(input); * --- */ string transcode(Decoder, Encoder)(string input) { DynamicArray!char array; auto decoder = Decoder(); auto encoder = Encoder(); foreach(c; decoder.decode(input)) { char[4] buffer; size_t len = encoder.encode(c, buffer); if (len) array.append(buffer[0..len]); } auto output = copy(array.data); array.free(); return cast(string)output; } ================================================ FILE: dlib/text/lexer.d ================================================ /* Copyright (c) 2016-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * General-purpose non-allocating lexical analyzer. * * Description: * Breaks the input string to a stream of lexemes according to a given delimiter dictionary. * Delimiters are symbols that separate sequences of characters (e.g. operators). * Lexemes are slices of the input string. * Assumes UTF-8 input. * Treats \r\n as a single \n. * * Copyright: Timur Gafarov 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov, Eugene Wissner, Roman Chistokhodov, ijet */ module dlib.text.lexer; import std.ascii; import std.range.interfaces; import dlib.text.utf8; /** * Lexical analyzer class */ class Lexer: InputRange!string { protected: string input; string[] delims; UTF8Decoder dec; uint index; dchar current; uint currentSize; public: bool ignoreWhitespaces = false; bool ignoreNewlines = false; this(string input, string[] delims) { this.input = input; this.delims = delims; this.dec.input = this.input; this.index = 0; advance(); } uint position() @property { return pos(); } string getLexeme() { string res = ""; int tStart = -1; while(true) { dchar c = current; if (c == UTF8_END || c == UTF8_ERROR) { if (tStart > -1) { res = input[tStart..pos]; tStart = -1; break; } else { res = ""; break; } } if (isNewline(c)) { if (tStart > -1) { res = input[tStart..pos]; tStart = -1; break; } else { if (c == '\r') { advance(); c = current; if (c == '\n') advance(); } else { advance(); } if (ignoreNewlines) { continue; } else { res = "\n"; break; } } } if (isWhitespace(c)) { if (tStart > -1) { res = input[tStart..pos]; tStart = -1; break; } else { advance(); if (ignoreWhitespaces) { continue; } else { res = " "; break; } } } string d = consumeDelimiter(); if (d.length) { if (tStart > -1) { res = input[tStart..pos]; } else { res = d; forwardJump(d.length); } break; } else { if (tStart == -1) { tStart = pos; } advance(); } } return res; } protected: uint pos() { return index-currentSize; } void advance() { current = dec.decodeNext(); currentSize = cast(uint)dec.index - index; index += currentSize; } void forwardJump(size_t numChars) { for(size_t i = 0; i < numChars; i++) { advance(); } } bool forwardCompare(string str) { UTF8Decoder dec2 = UTF8Decoder(str); size_t oldIndex = dec.index; bool res = true; int c1 = current; int c2 = dec2.decodeNext(); do { if (c2 == UTF8_END || c2 == UTF8_ERROR) break; if (c1 == UTF8_END && c2 == UTF8_END) { res = true; break; } if (c1 != UTF8_END && c1 != UTF8_ERROR && c2 != UTF8_END && c2 != UTF8_ERROR) { if (c1 != c2) { res = false; break; } } else { res = false; break; } c1 = dec.decodeNext(); c2 = dec2.decodeNext(); } while(c2 != UTF8_END && c2 != UTF8_ERROR); dec.index = oldIndex; return res; } bool isWhitespace(dchar c) { foreach(w; std.ascii.whitespace) { if (c == w) { return true; } } return false; } bool isNewline(dchar c) { return (c == '\n' || c == '\r'); } string consumeDelimiter() { size_t bestLen = 0; string bestStr = ""; foreach(d; delims) { if (forwardCompare(d)) { if (d.length > bestLen) { bestLen = d.length; bestStr = input[pos..pos+d.length]; } } } return bestStr; } // Range interface private: string _front; public: bool empty() { return _front.length == 0; } string front() { return _front; } void popFront() { _front = getLexeme(); } string moveFront() { _front = getLexeme(); return _front; } int opApply(scope int delegate(string) dg) { int result = 0; while(true) { string lexeme = getLexeme(); if (!lexeme.length) break; result = dg(lexeme); if (result) break; } return result; } int opApply(scope int delegate(size_t, string) dg) { int result = 0; size_t i = 0; while(true) { string lexeme = getLexeme(); if (!lexeme.length) break; result = dg(i, lexeme); if (result) break; i++; } return result; } } /// unittest { import std.array: array; import std.range: iota; string[] delims = [ "(", ")", ";", " ", "{", "}", ".", "\n", "\r", "=", "++", "<" ]; auto input = "for (int i=0; i 0) data.insertBack(cStr[0..offset]); addZero(); } /** * Construct from zero-terminated UTF-16 LE string */ this(const(wchar)* wStr) { wchar* utf16 = cast(wchar*)wStr; wchar utf16char; do { utf16char = *wStr; utf16++; if (utf16char) { if (utf16char < 0x80) { data.insertBack((utf16char >> 0 & 0x7F) | 0x00); } else if (utf16char < 0x0800) { data.insertBack((utf16char >> 6 & 0x1F) | 0xC0); data.insertBack((utf16char >> 0 & 0x3F) | 0x80); } else if (utf16char < 0x010000) { data.insertBack((utf16char >> 12 & 0x0F) | 0xE0); data.insertBack((utf16char >> 6 & 0x3F) | 0x80); data.insertBack((utf16char >> 0 & 0x3F) | 0x80); } else if (utf16char < 0x110000) { data.insertBack((utf16char >> 18 & 0x07) | 0xF0); data.insertBack((utf16char >> 12 & 0x3F) | 0x80); data.insertBack((utf16char >> 6 & 0x3F) | 0x80); data.insertBack((utf16char >> 0 & 0x3F) | 0x80); } } } while(utf16char); addZero(); } /** * Construct from an InputStream */ this(InputStream istrm) { data.resize(cast(size_t)istrm.size, 0); istrm.fillArray(data.data); addZero(); } void free() { data.free(); } auto opOpAssign(string op)(string s) if (op == "~") { removeZero(); data.insertBack(s); addZero(); return this; } auto opOpAssign(string op)(char c) if (op == "~") { removeZero(); data.insertBack(c); addZero(); return this; } auto opOpAssign(string op)(String s) if (op == "~") { String s1 = this; s1.removeZero(); s1 ~= s; s1.addZero(); return s1; } void reserve(size_t amount) { data.reserve(amount); } @property size_t length() { if (data.length == 0) return 0; else return data.length - 1; } @property string toString() const { if (data.length == 0) return ""; else return cast(string)data.readOnlyData[0..$-1]; } alias toString this; @property const(char)* ptr() const { return data.readOnlyData.ptr; } @property bool isDynamic() { return data.isDynamic; } /** * Range interface that iterates the string by Unicode code point (dchar), * i.e., foreach(dchar c; str.decode) */ auto decode() { return UTF8Decoder().decode(toString()); } } /// unittest { String s = "hello"; s ~= ", world"; s ~= '!'; assert(!s.isDynamic); string dStr = s; assert(dStr == "hello, world!"); s.free(); assert(s.length == 0); const(char)* cStr = "Hello!"; String s2 = String(cStr); assert(s2.toString == "Hello!"); import std.algorithm.comparison: equal; assert(equal(s2.decode, ['H', 'e', 'l', 'l', 'o', '!'])); auto istrm = new ArrayStream([104, 101, 108, 108, 111]); String s3 = String(istrm); assert(s3.toString == "hello"); } ================================================ FILE: dlib/text/utf16.d ================================================ /* Copyright (c) 2016-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * UTF-16 decoder and encoder * * Copyright: Timur Gafarov 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov, Roman Chistokhodov */ module dlib.text.utf16; import core.stdc.stdio; import dlib.core.memory; import dlib.container.array; import dlib.text.utf8; import dlib.text.utils; import dlib.text.common; enum ushort UTF16_HI_SURROGATE = 0xD800; enum ushort UTF16_LO_SURROGATE = 0xDC00; enum ushort UTF16_BOM_LE = 0xfeff; enum ushort UTF16_BOM_BE = 0xfffe; /** * UTF-16 LE decoder to use with dlib.text.encodings.transcode */ struct UTF16LEDecoder { // TODO: byte order public: /// Input string. Set it before decoding string input; /// Current index in an input string size_t index = 0; /// Current character index int character = 0; /** * Decode next character. * Returns: decoded code point, or UTF8_ERROR if error occured, or UTF8_END if input has no more characters. */ int decodeNext() { if (index >= input.length) return index == input.length ? DECODE_END : DECODE_ERROR; character++; wchar c = *cast(wchar*)(&input[index]); index += 2; return c; } /** * Check if decoder is in the end of input. */ bool eos() { return (index >= input.length); } /** * Range interface. */ auto decode(string s) { input = s; static struct ByDchar { private: UTF16LEDecoder _decoder; dchar _lastRead; public: this(UTF16LEDecoder decoder) { _decoder = decoder; _lastRead = cast(dchar)_decoder.decodeNext(); } bool empty() { return _lastRead == DECODE_END || _lastRead == DECODE_ERROR; } dchar front() { return _lastRead; } void popFront() { _lastRead = cast(dchar)_decoder.decodeNext(); } auto save() { return this; } } return ByDchar(this); } /// ditto auto decode() { return decode(input); } } /// unittest { wstring input = "Жå∑"; auto decoder = UTF16LEDecoder(cast(string)input); assert(decoder.decodeNext() == 'Ж'); assert(decoder.decodeNext() == 'å'); assert(decoder.decodeNext() == '∑'); } /** * UTF-16 LE encoder to use with dlib.text.encodings.transcode */ struct UTF16LEEncoder { /** * Encodes a Unicode code point to UTF-16 LE into user-provided buffer. * Returns number of bytes written, or 0 at error. */ size_t encode(uint ch, char[] buffer) { wchar[] wbuffer = cast(wchar[])buffer; if (ch > 0xFFFF) { wchar x = cast(wchar)ch; wchar vh = cast(wchar)(UTF16_HI_SURROGATE | ((((ch >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10)); wchar vl = cast(wchar)(UTF16_LO_SURROGATE | (x & ((1 << 10) - 1))); wbuffer[0] = vh; wbuffer[1] = vl; return 4; } else { wbuffer[0] = cast(wchar)ch; return 2; } } } /// unittest { UTF16LEEncoder enc; char[4] buffer; size_t numBytes = enc.encode('Ж', buffer); assert(numBytes == 2); assert(cast(wchar[])(buffer[0..numBytes]) == [0x0416]); } /** * Converts UTF-8 to UTF-16 * Will be deprecated soon, use transcode!(UTF8Decoder, UTF16LEEncoder) instead */ wchar[] convertUTF8toUTF16(string s, bool nullTerm = false) { Array!wchar array; wchar[] output; UTF8Decoder dec = UTF8Decoder(s); while (!dec.eos) { int code = dec.decodeNext(); if (code == UTF8_ERROR) { array.free(); return output; } dchar ch = cast(dchar)code; if (ch > 0xFFFF) { // Split ch up into a surrogate pair as it is over 16 bits long. wchar x = cast(wchar)ch; auto vh = UTF16_HI_SURROGATE | ((((ch >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); auto vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); array.append(cast(wchar)vh); array.append(cast(wchar)vl); } else { array.append(cast(wchar)ch); } } if (nullTerm) { array.append(0); } output = copy(array.data); array.free(); return output; } /** * Converts UTF-16 zero-terminated string to UTF-8 */ char[] convertUTF16ztoUTF8(wchar* s, bool nullTerm = false) { Array!char array; char[] output; wchar* utf16 = s; wchar utf16char; do { utf16char = *utf16; utf16++; if (utf16char) { if (utf16char < 0x80) { array.append((utf16char >> 0 & 0x7F) | 0x00); } else if (utf16char < 0x0800) { array.append((utf16char >> 6 & 0x1F) | 0xC0); array.append((utf16char >> 0 & 0x3F) | 0x80); } else if (utf16char < 0x010000) { array.append((utf16char >> 12 & 0x0F) | 0xE0); array.append((utf16char >> 6 & 0x3F) | 0x80); array.append((utf16char >> 0 & 0x3F) | 0x80); } else if (utf16char < 0x110000) { array.append((utf16char >> 18 & 0x07) | 0xF0); array.append((utf16char >> 12 & 0x3F) | 0x80); array.append((utf16char >> 6 & 0x3F) | 0x80); array.append((utf16char >> 0 & 0x3F) | 0x80); } } } while (utf16char); if (nullTerm) { array.append(0); } output = copy(array.data); array.free(); return output; } ================================================ FILE: dlib/text/utf8.d ================================================ /* Copyright (c) 2015-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * UTF-8 encoder and decoder * * Copyright: Timur Gafarov 2015-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov, Roman Chistokhodov */ module dlib.text.utf8; import dlib.text.common; /// Constant to return from UTF8Decoder on the end of string. enum UTF8_END = DECODE_END; /// Constant to return from UTF8Decoder when error occurs. enum UTF8_ERROR = DECODE_ERROR; /** * UTF-8 decoder to use with dlib.text.encodings.transcode */ struct UTF8Decoder { public: /// Input string. Set it before decoding string input; /// Current index in an input string size_t index = 0; /// Current character index int character = 0; private: int get() { if (index >= input.length) return UTF8_END; auto c = input[index] & 0xFF; index++; return c; } int cont() { int c = get(); return ((c & 0xC0) == 0x80) ? (c & 0x3F): UTF8_ERROR; } public: /** * Decode next character. * Returns: decoded code point, or UTF8_ERROR if error occured, or UTF8_END if input has no more characters. */ int decodeNext() { int c; // the first byte of the character int r; // the result if (index >= input.length) return index == input.length ? UTF8_END : UTF8_ERROR; character++; c = get(); // Zero continuation (0 to 127) if ((c & 0x80) == 0) return c; // One continuation (128 to 2047) if ((c & 0xE0) == 0xC0) { int c1 = cont(); if (c1 >= 0) { r = ((c & 0x1F) << 6) | c1; return r >= 128 ? r : UTF8_ERROR; } } // Two continuation (2048 to 55295 and 57344 to 65535) else if ((c & 0xF0) == 0xE0) { int c1 = cont(); int c2 = cont(); if ((c1 | c2) >= 0) { r = ((c & 0x0F) << 12) | (c1 << 6) | c2; return r >= 2048 && (r < 55296 || r > 57343) ? r : UTF8_ERROR; } } // Three continuation (65536 to 1114111) else if ((c & 0xF8) == 0xF0) { int c1 = cont(); int c2 = cont(); int c3 = cont(); if ((c1 | c2 | c3) >= 0) { return (((c & 0x0F) << 18) | (c1 << 12) | (c2 << 6) | c3); } } return UTF8_ERROR; } /** * Check if decoder is in the end of input. */ bool eos() { return (index >= input.length); } /** * Range interface. */ auto decode(string s) { input = s; static struct ByDchar { private: UTF8Decoder _decoder; dchar _lastRead; public: this(UTF8Decoder decoder) { _decoder = decoder; _lastRead = cast(dchar)_decoder.decodeNext(); } bool empty() { return _lastRead == UTF8_END || _lastRead == UTF8_ERROR; } dchar front() { return _lastRead; } void popFront() { _lastRead = cast(dchar)_decoder.decodeNext(); } auto save() { return this; } } return ByDchar(this); } /// ditto auto decode() { return decode(input); } /// unittest { auto decoder = UTF8Decoder("Eng 日本語 Кир ©€"); import std.algorithm: equal; assert(equal(decoder.decode(), "Eng 日本語 Кир ©€"d)); auto range = decoder.decode(); auto saved = range.save; range.popFront(); range.popFront(); range.popFront(); range.popFront(); range.popFront(); assert(equal(range, "本語 Кир ©€"d)); assert(equal(saved, "Eng 日本語 Кир ©€"d)); } } /// unittest { { auto decoder = UTF8Decoder("Eng 日本語 Кир ©€\xF0\x90\x8D\x88"); assert(decoder.decodeNext() == 'E'); assert(decoder.decodeNext() == 'n'); assert(decoder.decodeNext() == 'g'); assert(decoder.decodeNext() == ' '); assert(decoder.decodeNext() == '日'); assert(decoder.decodeNext() == '本'); assert(decoder.decodeNext() == '語'); assert(decoder.decodeNext() == ' '); assert(decoder.decodeNext() == 'К'); assert(decoder.decodeNext() == 'и'); assert(decoder.decodeNext() == 'р'); assert(decoder.decodeNext() == ' '); assert(decoder.decodeNext() == '©'); assert(decoder.decodeNext() == '€'); assert(decoder.decodeNext() == 0x10348); assert(decoder.decodeNext() == UTF8_END); assert(decoder.get() == UTF8_END); assert(decoder.eos()); } { auto decoder = UTF8Decoder("日本語"[0..$-1]); assert(decoder.decodeNext() == '日'); assert(decoder.decodeNext() == '本'); assert(decoder.decodeNext() == UTF8_ERROR); } } /** * UTF-8 encoder to use with dlib.text.encodings.transcode */ struct UTF8Encoder { /** * Encodes a Unicode code point to UTF-8 into user-provided buffer. * Returns number of bytes written, or 0 at error. */ size_t encode(uint c, char[] buffer) { if (c <= 0x7F) { // Plain ASCII buffer[0] = cast(char)c; return 1; } else if (c <= 0x07FF) { // 2-byte unicode buffer[0] = cast(char)(((c >> 6) & 0x1F) | 0xC0); buffer[1] = cast(char)(((c >> 0) & 0x3F) | 0x80); return 2; } else if (c <= 0xFFFF) { // 3-byte unicode buffer[0] = cast(char)(((c >> 12) & 0x0F) | 0xE0); buffer[1] = cast(char)(((c >> 6) & 0x3F) | 0x80); buffer[2] = cast(char)(((c >> 0) & 0x3F) | 0x80); return 3; } else if (c <= 0x10FFFF) { // 4-byte unicode buffer[0] = cast(char)(((c >> 18) & 0x07) | 0xF0); buffer[1] = cast(char)(((c >> 12) & 0x3F) | 0x80); buffer[2] = cast(char)(((c >> 6) & 0x3F) | 0x80); buffer[3] = cast(char)(((c >> 0) & 0x3F) | 0x80); return 4; } else { // error return 0; } } } /// unittest { UTF8Encoder enc; char[4] buffer; size_t numBytes = enc.encode('Ж', buffer); assert(numBytes == 2); assert(buffer[0..numBytes] == [0xD0, 0x96]); } ================================================ FILE: dlib/text/utils.d ================================================ /* Copyright (c) 2016-2025 Timur Gafarov Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Text processing utils * * Copyright: Timur Gafarov 2016-2025. * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Timur Gafarov */ module dlib.text.utils; import dlib.core.memory; /// Make an copy of a string in unmanaged memory T[] copy(T)(T[] b) { auto res = New!(T[])(b.length); foreach(i, c; b) res[i] = c; return res; } /// unittest { auto str = "hello".dup; auto c = copy(str); assert(c == str); Delete(c); } /// Make an immutable copy of a string in unmanaged memory immutable(T)[] immutableCopy(T)(immutable(T)[] b) { auto res = New!(T[])(b.length); foreach(i, c; b) res[i] = c; return cast(immutable(T)[])res; } /// Concatenates two strings to a new string in unmanaged memory string catStr(string s1, string s2) { char[] buffer = New!(char[])(s1.length + s2.length); size_t i, j; for(i = 0; i < s1.length; i++) { buffer[i] = s1[i]; } for(j = 0; j < s2.length; j++) { buffer[i+j] = s2[j]; } return cast(string)buffer; } /// unittest { auto str1 = "hello"; auto str2 = " world"; auto cat = catStr(str1, str2); assert(cat == "hello world"); Delete(cat); } ================================================ FILE: dub.json ================================================ { "name": "dlib", "description": "D language utility library", "homepage": "http://github.com/gecko0307/dlib", "license": "BSL-1.0", "authors": [ "Timur Gafarov", "Martin Cejp", "Andrey Penechko", "Vadim Lopatin", "Nick Papanastasiou", "Oleg Baharev", "Roman Chistokhodov", "Eugene Wissner", "Roman Vlasov", "Basile Burg", "Valeriy Fedotov", "Ferhat Kurtulmuş" ], "importPaths": [ "." ], "buildRequirements":[ "allowWarnings" ], "lflags-linux-gdc": ["-lz"], "configurations": [ { "name": "library", "targetType": "library", "sourcePaths": ["dlib"] }, { "name": "import", "targetType": "sourceLibrary", "sourceFiles-posix": ["libdlib.a"], "sourceFiles-windows": ["dlib.lib"] } ] }