io/image.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | /// @file image.h | ||
| 3 | /// @brief Float RGBA images: loading, EXR output, sRGB and mips. | ||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "ernie/export.h" | ||
| 7 | |||
| 8 | #include "ernie/util/tensor.h" | ||
| 9 | |||
| 10 | #include "ernie/util/glm.h" | ||
| 11 | |||
| 12 | #include <cstdint> | ||
| 13 | #include <filesystem> | ||
| 14 | #include <vector> | ||
| 15 | |||
| 16 | namespace ernie { | ||
| 17 | |||
| 18 | /// @brief How 8- and 16-bit stored values map to linear values. | ||
| 19 | enum class ImageEncoding { | ||
| 20 | /// IEC 61966-2-1 sRGB transfer function, for colour textures. | ||
| 21 | Srgb, | ||
| 22 | /// Stored values are linear, for normal and data maps. | ||
| 23 | Linear, | ||
| 24 | }; | ||
| 25 | |||
| 26 | /// @brief How `MipChain` filters each level. | ||
| 27 | enum class MipFilter { | ||
| 28 | /// 2 x 2 box filter of every channel. | ||
| 29 | Average, | ||
| 30 | /// Box filter of tangent-space normals (rgb in [0, 1]), | ||
| 31 | /// renormalised. | ||
| 32 | UnitVector, | ||
| 33 | /// Box filter of tangent-space normals in slope space. rgb holds | ||
| 34 | /// the normal of the mean slope (z at least `kMinNormalZ`); | ||
| 35 | /// alpha the per-axis slope deviation as `EncodeSlopeDeviation` | ||
| 36 | /// (0 at level 0). Shaders recover the variance filtering hides. | ||
| 37 | NormalSlopes, | ||
| 38 | }; | ||
| 39 | |||
| 40 | /// Least normal z slopes are taken from, keeping slopes below 20. | ||
| 41 | inline constexpr float kMinNormalZ = 0.05f; | ||
| 42 | |||
| 43 | /// @brief Packs a slope deviation into [0, 1) for 8-bit storage. | ||
| 44 | /// @param sigma The deviation, 0 or more. | ||
| 45 | /// @return sigma / (1 + sigma), finest near 0. | ||
| 46 | ERNIE_API float EncodeSlopeDeviation(float sigma); | ||
| 47 | /// @brief Inverts `EncodeSlopeDeviation`. | ||
| 48 | /// @param encoded The stored value. | ||
| 49 | /// @return The slope deviation. | ||
| 50 | ERNIE_API float DecodeSlopeDeviation(float encoded); | ||
| 51 | |||
| 52 | /// @brief An RGBA image of linear floats, rows top to bottom. | ||
| 53 | 195 | struct ERNIE_API Image { | |
| 54 | /// Width, in pixels. | ||
| 55 | std::uint32_t width = 0; | ||
| 56 | /// Height, in pixels. | ||
| 57 | std::uint32_t height = 0; | ||
| 58 | /// Pixels, row by row. | ||
| 59 | std::vector<glm::vec4> pixels; | ||
| 60 | |||
| 61 | 1 | Image() = default; | |
| 62 | /// @brief Allocates a filled image. | ||
| 63 | /// @param w Width, in pixels. | ||
| 64 | /// @param h Height, in pixels. | ||
| 65 | /// @param fill The initial value of every pixel. | ||
| 66 | 53 | Image(std::uint32_t w, std::uint32_t h, const glm::vec4& fill = glm::vec4(0.0f)) | |
| 67 | 53 | : width(w), height(h), pixels(static_cast<std::size_t>(w) * h, fill) {} | |
| 68 | |||
| 69 | /// @return The pixel at (`x`, `y`). | ||
| 70 | 743 | glm::vec4& At(std::uint32_t x, std::uint32_t y) { | |
| 71 | 741 | return pixels[static_cast<std::size_t>(y) * width + x]; | |
| 72 | } | ||
| 73 | |||
| 74 | /// @return The pixel at (`x`, `y`). | ||
| 75 | 705 | const glm::vec4& At(std::uint32_t x, std::uint32_t y) const { | |
| 76 | 705 | return pixels[static_cast<std::size_t>(y) * width + x]; | |
| 77 | } | ||
| 78 | |||
| 79 | /// @return The mean of every pixel. | ||
| 80 | glm::vec4 Mean() const; | ||
| 81 | }; | ||
| 82 | |||
| 83 | /// @brief Loads a PNG (8/16-bit), JPEG or OpenEXR file. | ||
| 84 | /// Grey replicates to rgb; missing alpha reads as 1. EXR reads | ||
| 85 | /// scanline and tiled images in any compression, DWAA/DWAB included. | ||
| 86 | /// @param path The file. | ||
| 87 | /// @param encoding How PNG and JPEG values decode; EXR is linear. | ||
| 88 | /// @return The image. | ||
| 89 | /// @throws std::runtime_error If the file cannot be read or decoded, | ||
| 90 | /// or is not .png, .jpg, .jpeg or .exr. | ||
| 91 | ERNIE_API Image LoadImageFile(const std::filesystem::path& path, ImageEncoding encoding); | ||
| 92 | |||
| 93 | /// @brief Tests for a .png, .jpg or .jpeg extension, in any case. | ||
| 94 | /// @param path The file. | ||
| 95 | /// @return Whether it is a PNG or JPEG, read by stb. | ||
| 96 | ERNIE_API bool IsPngOrJpeg(const std::filesystem::path& path); | ||
| 97 | |||
| 98 | /// @brief Writes a CPU tensor as a ZIP-compressed OpenEXR file. | ||
| 99 | /// Channels are float32, or uint32 for uint32 data such as ids; | ||
| 100 | /// narrower integers become exact floats. One channel is Y, two RG, | ||
| 101 | /// three RGB, four RGBA, more are numbered. | ||
| 102 | /// @param path The file. | ||
| 103 | /// @param image (rows, cols) or (rows, cols, channels), rows top to | ||
| 104 | /// bottom. | ||
| 105 | /// @throws std::invalid_argument For another rank or type. | ||
| 106 | /// @throws std::runtime_error If the file cannot be written. | ||
| 107 | ERNIE_API void WriteExr(const std::filesystem::path& path, const Tensor& image); | ||
| 108 | |||
| 109 | /// @brief Decodes one sRGB channel to linear (IEC 61966-2-1). | ||
| 110 | /// @param value The encoded value in [0, 1]. | ||
| 111 | /// @return The linear value. | ||
| 112 | ERNIE_API float SrgbToLinear(float value); | ||
| 113 | /// @brief Encodes one linear channel to sRGB (IEC 61966-2-1). | ||
| 114 | /// @param value The linear value in [0, 1]. | ||
| 115 | /// @return The encoded value. | ||
| 116 | ERNIE_API float LinearToSrgb(float value); | ||
| 117 | |||
| 118 | /// @brief Builds the mip chain down to 1 x 1. | ||
| 119 | /// Level n is max(1, floor(size / 2^n)) per side, as WebGPU expects; | ||
| 120 | /// each texel filters the 2 x 2 above it, clamped at odd edges. | ||
| 121 | /// @param image Level 0. | ||
| 122 | /// @param filter How levels are filtered. | ||
| 123 | /// @return Every level, level 0 first. | ||
| 124 | /// | ||
| 125 | /// References: | ||
| 126 | /// - M. Toksvig, "Mipmapping Normal Maps", Journal of Graphics Tools | ||
| 127 | /// 10(3), 2005. | ||
| 128 | /// - M. Olano and D. Baker, "LEAN Mapping", I3D 2010. | ||
| 129 | ERNIE_API std::vector<Image> MipChain(const Image& image, MipFilter filter); | ||
| 130 | |||
| 131 | /// @brief Quantises to 8-bit RGBA. | ||
| 132 | /// @param image The image. | ||
| 133 | /// @param encoding `Srgb` encodes rgb as sRGB; alpha stays linear. | ||
| 134 | /// @return Four bytes per pixel. | ||
| 135 | ERNIE_API std::vector<std::uint8_t> ToRgba8(const Image& image, ImageEncoding encoding); | ||
| 136 | |||
| 137 | /// @brief A texture tensor as an image. | ||
| 138 | /// @details Grey replicates into rgb and a missing alpha reads as 1. | ||
| 139 | /// @param tensor A float32 (linear) or uint8 (rows, cols) or (rows, | ||
| 140 | /// cols, 1, 3 or 4) CPU tensor. | ||
| 141 | /// @param encoding How uint8 colour is encoded: sRGB or linear. | ||
| 142 | /// @return The image. | ||
| 143 | /// @throws std::invalid_argument For another kind of tensor. | ||
| 144 | ERNIE_API Image ImageFromTensor(const Tensor& tensor, ImageEncoding encoding); | ||
| 145 | |||
| 146 | } // namespace ernie | ||
| 147 |