io/ies.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/io/ies.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <cctype> | ||
| 6 | #include <cerrno> | ||
| 7 | #include <cmath> | ||
| 8 | #include <cstdlib> | ||
| 9 | #include <string> | ||
| 10 | #include <vector> | ||
| 11 | |||
| 12 | #include "chrono_batch_render/util/error.h" | ||
| 13 | #include "chrono_batch_render/util/text.h" | ||
| 14 | |||
| 15 | namespace chrono::batch_render { | ||
| 16 | namespace { | ||
| 17 | |||
| 18 | constexpr uint32_t kTypeC = 1; | ||
| 19 | |||
| 20 | class Numbers { | ||
| 21 | public: | ||
| 22 | 6 | explicit Numbers(std::string_view text) : m_text(text) {} | |
| 23 | |||
| 24 | 122 | double Next() { | |
| 25 | 239 | while (m_at < m_text.size() && | |
| 26 | 238 | (std::isspace(static_cast<unsigned char>(m_text[m_at])) || m_text[m_at] == ',')) { | |
| 27 | 117 | ++m_at; | |
| 28 | } | ||
| 29 | size_t stop = m_at; | ||
| 30 | 309 | while (stop < m_text.size() && !std::isspace(static_cast<unsigned char>(m_text[stop])) && | |
| 31 | 188 | m_text[stop] != ',') { | |
| 32 | 187 | ++stop; | |
| 33 | } | ||
| 34 | 122 | const std::string token(m_text.substr(m_at, stop - m_at)); | |
| 35 | 122 | char* end = nullptr; | |
| 36 | 122 | errno = 0; | |
| 37 | 122 | const double value = std::strtod(token.c_str(), &end); | |
| 38 | 122 | if (end == token.c_str() || errno == ERANGE) { | |
| 39 | 1 | throw Error("an IES profile ends early or holds {:.20}", | |
| 40 | 1 | m_text.substr(m_at, m_text.find('\n', m_at) - m_at)); | |
| 41 | } | ||
| 42 | 121 | m_at += size_t(end - token.c_str()); | |
| 43 | 121 | return value; | |
| 44 | 121 | } | |
| 45 | |||
| 46 | 31 | std::vector<double> Take(size_t count) { | |
| 47 | 31 | std::vector<double> values(count); | |
| 48 | 126 | for (double& value : values) { | |
| 49 | 96 | value = Next(); | |
| 50 | } | ||
| 51 | 30 | return values; | |
| 52 | 1 | } | |
| 53 | |||
| 54 | private: | ||
| 55 | std::string_view m_text; | ||
| 56 | size_t m_at = 0; | ||
| 57 | }; | ||
| 58 | |||
| 59 | 264613 | double Interpolate(const std::vector<double>& angles, const std::vector<double>& values, | |
| 60 | double angle) { | ||
| 61 | 264613 | if (angles.size() == 1) { | |
| 62 | 1805 | return values[0]; | |
| 63 | } | ||
| 64 | 262808 | if (angle < angles.front() || angle > angles.back()) { | |
| 65 | return 0; | ||
| 66 | } | ||
| 67 | 262808 | const auto upper = std::upper_bound(angles.begin(), angles.end(), angle); | |
| 68 | 262808 | const size_t high = std::min(size_t(upper - angles.begin()), angles.size() - 1); | |
| 69 | 262808 | const size_t low = high - 1; | |
| 70 | 262808 | const double span = angles[high] - angles[low]; | |
| 71 | 262808 | const double t = span > 0 ? (angle - angles[low]) / span : 0; | |
| 72 | 262808 | return values[low] + t * (values[high] - values[low]); | |
| 73 | } | ||
| 74 | |||
| 75 | 1444 | double Fold(double horizontal, double first, double last) { | |
| 76 | 1444 | if (last <= 0) { | |
| 77 | return 0; | ||
| 78 | } | ||
| 79 | 722 | if (first == 90 && last == 270) { | |
| 80 | ✗ | return horizontal < 90 ? 180 - horizontal | |
| 81 | ✗ | : horizontal > 270 ? 540 - horizontal | |
| 82 | ✗ | : horizontal; | |
| 83 | } | ||
| 84 | 722 | if (last <= 90) { | |
| 85 | 361 | horizontal = std::fmod(horizontal, 180.0); | |
| 86 | 361 | return horizontal > 90 ? 180 - horizontal : horizontal; | |
| 87 | } | ||
| 88 | 361 | if (last <= 180) { | |
| 89 | ✗ | return horizontal > 180 ? 360 - horizontal : horizontal; | |
| 90 | } | ||
| 91 | return horizontal; | ||
| 92 | } | ||
| 93 | |||
| 94 | } | ||
| 95 | |||
| 96 | 10 | bool IsIes(const std::filesystem::path& path) { | |
| 97 | 30 | return Lowercase(path.extension().string()) == ".ies"; | |
| 98 | } | ||
| 99 | |||
| 100 | 7 | Image ReadIes(std::string_view text) { | |
| 101 | 7 | const size_t tilt = text.find("TILT="); | |
| 102 | 7 | if (tilt == std::string_view::npos) { | |
| 103 | 2 | throw Error("an IES profile has no TILT line"); | |
| 104 | } | ||
| 105 | 6 | const size_t line = text.find('\n', tilt); | |
| 106 | 6 | const std::string_view mode = text.substr(tilt + 5, line - tilt - 5); | |
| 107 | 6 | Numbers numbers(line == std::string_view::npos ? std::string_view() : text.substr(line + 1)); | |
| 108 | 6 | if (mode.starts_with("INCLUDE")) { | |
| 109 | 1 | numbers.Next(); | |
| 110 | 1 | const size_t pairs = size_t(numbers.Next()); | |
| 111 | 2 | numbers.Take(2 * pairs); | |
| 112 | } | ||
| 113 | 6 | numbers.Take(2); | |
| 114 | 6 | const double multiplier = numbers.Next(); | |
| 115 | 6 | const size_t verticalCount = size_t(numbers.Next()); | |
| 116 | 6 | const size_t horizontalCount = size_t(numbers.Next()); | |
| 117 | 6 | const uint32_t type = uint32_t(numbers.Next()); | |
| 118 | 6 | numbers.Take(7); | |
| 119 | 6 | if (type != kTypeC) { | |
| 120 | 1 | throw Error("an IES profile has photometric type {}, and only type C is supported", type); | |
| 121 | } | ||
| 122 | 5 | if (verticalCount == 0 || horizontalCount == 0) { | |
| 123 | ✗ | throw Error("an IES profile has no angles"); | |
| 124 | } | ||
| 125 | 5 | const std::vector<double> vertical = numbers.Take(verticalCount); | |
| 126 | 5 | std::vector<double> horizontal = numbers.Take(horizontalCount); | |
| 127 | 6 | std::vector<std::vector<double>> candela(horizontalCount); | |
| 128 | 5 | double peak = 0; | |
| 129 | 12 | for (std::vector<double>& plane : candela) { | |
| 130 | 15 | plane = numbers.Take(verticalCount); | |
| 131 | 22 | for (double& value : plane) { | |
| 132 | 15 | value = std::max(value * multiplier, 0.0); | |
| 133 | 19 | peak = std::max(peak, value); | |
| 134 | } | ||
| 135 | } | ||
| 136 | |||
| 137 | 4 | if (horizontal.front() == 0 && horizontal.back() > 180 && horizontal.back() < 360) { | |
| 138 | 1 | horizontal.push_back(360); | |
| 139 | 1 | candela.push_back(candela.front()); | |
| 140 | } | ||
| 141 | 4 | Image image(kIesColumns, kIesRows, {0, 0, 0, 1}); | |
| 142 | 4 | if (peak <= 0) { | |
| 143 | return image; | ||
| 144 | } | ||
| 145 | 4 | std::vector<double> column(verticalCount); | |
| 146 | 1448 | for (uint32_t x = 0; x < kIesColumns; ++x) { | |
| 147 | 1444 | const double around = | |
| 148 | 1444 | Fold(360.0 * x / (kIesColumns - 1), horizontal.front(), horizontal.back()); | |
| 149 | 4693 | for (size_t v = 0; v < verticalCount; ++v) { | |
| 150 | 3249 | std::vector<double> across(horizontal.size()); | |
| 151 | 9386 | for (size_t h = 0; h < horizontal.size(); ++h) { | |
| 152 | 6137 | across[h] = candela[h][v]; | |
| 153 | } | ||
| 154 | 3249 | column[v] = Interpolate(horizontal, across, around); | |
| 155 | 3249 | } | |
| 156 | 262808 | for (uint32_t y = 0; y < kIesRows; ++y) { | |
| 157 | 261364 | const float value = | |
| 158 | 261364 | float(Interpolate(vertical, column, 180.0 * y / (kIesRows - 1)) / peak); | |
| 159 | 261364 | image.Pixel(x, y) = {value, value, value, 1}; | |
| 160 | } | ||
| 161 | } | ||
| 162 | 4 | return image; | |
| 163 | 6 | } | |
| 164 | |||
| 165 | } | ||
| 166 |