chrono_batch_render/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
93 / 98
94.9%
Branches:
78 / 99
78.8%
Functions:
6 / 6, 1 excl
100.0%

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
4/4
✓ Branch 0 taken 238 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 122 times.
✓ Branch 3 taken 116 times.
239 while (m_at < m_text.size() &&
26
4/4
✓ Branch 0 taken 122 times.
✓ Branch 1 taken 116 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 121 times.
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
4/4
✓ Branch 0 taken 308 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 188 times.
✓ Branch 3 taken 120 times.
309 while (stop < m_text.size() && !std::isspace(static_cast<unsigned char>(m_text[stop])) &&
31
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 187 times.
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
3/4
✓ Branch 0 taken 121 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 121 times.
✗ Branch 3 not taken.
122 if (end == token.c_str() || errno == ERANGE) {
39 1 throw Error("an IES profile ends early or holds {:.20}",
40
2/2
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
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
2/2
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 30 times.
126 for (double& value : values) {
49
1/1
✓ Branch 1 taken 95 times.
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
2/2
✓ Branch 0 taken 1805 times.
✓ Branch 1 taken 262808 times.
264613 if (angles.size() == 1) {
62 1805 return values[0];
63 }
64
2/4
✓ Branch 0 taken 262808 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 262808 times.
✗ Branch 3 not taken.
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
2/2
✓ Branch 0 taken 1450 times.
✓ Branch 1 taken 261358 times.
262808 const size_t high = std::min(size_t(upper - angles.begin()), angles.size() - 1);
69 262808 const size_t low = high - 1;
70
1/2
✓ Branch 0 taken 262808 times.
✗ Branch 1 not taken.
262808 const double span = angles[high] - angles[low];
71
1/2
✓ Branch 0 taken 262808 times.
✗ Branch 1 not taken.
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
2/2
✓ Branch 0 taken 722 times.
✓ Branch 1 taken 722 times.
1444 if (last <= 0) {
77 return 0;
78 }
79
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 722 times.
722 if (first == 90 && last == 270) {
80
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ return horizontal < 90 ? 180 - horizontal
81
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ : horizontal > 270 ? 540 - horizontal
82 ✗ : horizontal;
83 }
84
2/2
✓ Branch 0 taken 361 times.
✓ Branch 1 taken 361 times.
722 if (last <= 90) {
85 361 horizontal = std::fmod(horizontal, 180.0);
86
2/2
✓ Branch 0 taken 178 times.
✓ Branch 1 taken 183 times.
361 return horizontal > 90 ? 180 - horizontal : horizontal;
87 }
88
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 361 times.
361 if (last <= 180) {
89
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ return horizontal > 180 ? 360 - horizontal : horizontal;
90 }
91 return horizontal;
92 }
93
94 }
95
96 10 bool IsIes(const std::filesystem::path& path) {
97
1/1
✓ Branch 2 taken 10 times.
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
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 6 times.
7 if (tilt == std::string_view::npos) {
103
2/2
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
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
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 Numbers numbers(line == std::string_view::npos ? std::string_view() : text.substr(line + 1));
108
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 5 times.
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
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 5 times.
6 if (type != kTypeC) {
120
1/1
✓ Branch 2 taken 1 time.
1 throw Error("an IES profile has photometric type {}, and only type C is supported", type);
121 }
122
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (verticalCount == 0 || horizontalCount == 0) {
123
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("an IES profile has no angles");
124 }
125 5 const std::vector<double> vertical = numbers.Take(verticalCount);
126
1/1
✓ Branch 1 taken 5 times.
5 std::vector<double> horizontal = numbers.Take(horizontalCount);
127
1/1
✓ Branch 1 taken 5 times.
6 std::vector<std::vector<double>> candela(horizontalCount);
128 5 double peak = 0;
129
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
12 for (std::vector<double>& plane : candela) {
130
1/1
✓ Branch 1 taken 7 times.
15 plane = numbers.Take(verticalCount);
131
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 7 times.
22 for (double& value : plane) {
132
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 value = std::max(value * multiplier, 0.0);
133
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 11 times.
19 peak = std::max(peak, value);
134 }
135 }
136
137
4/6
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
4 if (horizontal.front() == 0 && horizontal.back() > 180 && horizontal.back() < 360) {
138
1/1
✓ Branch 1 taken 1 time.
1 horizontal.push_back(360);
139
1/1
✓ Branch 1 taken 1 time.
1 candela.push_back(candela.front());
140 }
141
1/1
✓ Branch 1 taken 4 times.
4 Image image(kIesColumns, kIesRows, {0, 0, 0, 1});
142
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (peak <= 0) {
143 return image;
144 }
145
1/1
✓ Branch 1 taken 4 times.
4 std::vector<double> column(verticalCount);
146
2/2
✓ Branch 0 taken 1444 times.
✓ Branch 1 taken 4 times.
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
2/2
✓ Branch 1 taken 3249 times.
✓ Branch 2 taken 1444 times.
4693 for (size_t v = 0; v < verticalCount; ++v) {
150
1/1
✓ Branch 1 taken 3249 times.
3249 std::vector<double> across(horizontal.size());
151
2/2
✓ Branch 0 taken 6137 times.
✓ Branch 1 taken 3249 times.
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
2/2
✓ Branch 0 taken 261364 times.
✓ Branch 1 taken 1444 times.
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