Blender  V2.93
util_ies.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2011-2018 Blender Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <algorithm>
18 
19 #include "util/util_foreach.h"
20 #include "util/util_ies.h"
21 #include "util/util_math.h"
22 #include "util/util_string.h"
23 
25 
26 // NOTE: For some reason gcc-7.2 does not instantiate this versio of allocator
27 // gere (used in IESTextParser). Works fine for gcc-6, gcc-7.3 and gcc-8.
28 //
29 // TODO(sergey): Get to the root of this issue, or confirm this i a compiler
30 // issue.
31 template class GuardedAllocator<char>;
32 
33 bool IESFile::load(const string &ies)
34 {
35  clear();
36  if (!parse(ies) || !process()) {
37  clear();
38  return false;
39  }
40  return true;
41 }
42 
44 {
45  intensity.clear();
46  v_angles.clear();
47  h_angles.clear();
48 }
49 
51 {
52  if (v_angles.size() && h_angles.size() > 0) {
53  return 2 + h_angles.size() + v_angles.size() + h_angles.size() * v_angles.size();
54  }
55  return 0;
56 }
57 
58 void IESFile::pack(float *data)
59 {
60  if (v_angles.size() && h_angles.size()) {
61  *(data++) = __int_as_float(h_angles.size());
62  *(data++) = __int_as_float(v_angles.size());
63 
64  memcpy(data, &h_angles[0], h_angles.size() * sizeof(float));
65  data += h_angles.size();
66  memcpy(data, &v_angles[0], v_angles.size() * sizeof(float));
67  data += v_angles.size();
68 
69  for (int h = 0; h < intensity.size(); h++) {
70  memcpy(data, &intensity[h][0], v_angles.size() * sizeof(float));
71  data += v_angles.size();
72  }
73  }
74 }
75 
77  public:
79  char *data;
80 
81  IESTextParser(const string &str) : text(str.begin(), str.end())
82  {
83  std::replace(text.begin(), text.end(), ',', ' ');
84  data = strstr(&text[0], "\nTILT=");
85  }
86 
87  bool eof()
88  {
89  return (data == NULL) || (data[0] == '\0');
90  }
91 
92  double get_double()
93  {
94  if (eof()) {
95  return 0.0;
96  }
97  char *old_data = data;
98  double val = strtod(data, &data);
99  if (data == old_data) {
100  data = NULL;
101  return 0.0;
102  }
103  return val;
104  }
105 
106  long get_long()
107  {
108  if (eof()) {
109  return 0;
110  }
111  char *old_data = data;
112  long val = strtol(data, &data, 10);
113  if (data == old_data) {
114  data = NULL;
115  return 0;
116  }
117  return val;
118  }
119 };
120 
121 bool IESFile::parse(const string &ies)
122 {
123  if (ies.empty()) {
124  return false;
125  }
126 
127  IESTextParser parser(ies);
128  if (parser.eof()) {
129  return false;
130  }
131 
132  /* Handle the tilt data block. */
133  if (strncmp(parser.data, "\nTILT=INCLUDE", 13) == 0) {
134  parser.data += 13;
135  parser.get_double(); /* Lamp to Luminaire geometry */
136  int num_tilt = parser.get_long(); /* Amount of tilt angles and factors */
137  /* Skip over angles and factors. */
138  for (int i = 0; i < 2 * num_tilt; i++) {
139  parser.get_double();
140  }
141  }
142  else {
143  /* Skip to next line. */
144  parser.data = strstr(parser.data + 1, "\n");
145  }
146 
147  if (parser.eof()) {
148  return false;
149  }
150  parser.data++;
151 
152  parser.get_long(); /* Number of lamps */
153  parser.get_double(); /* Lumens per lamp */
154  double factor = parser.get_double(); /* Candela multiplier */
155  int v_angles_num = parser.get_long(); /* Number of vertical angles */
156  int h_angles_num = parser.get_long(); /* Number of horizontal angles */
157  type = (IESType)parser.get_long(); /* Photometric type */
158 
159  /* TODO(lukas): Test whether the current type B processing can also deal with type A files.
160  * In theory the only difference should be orientation which we ignore anyways, but with IES you
161  * never know...
162  */
163  if (type != TYPE_B && type != TYPE_C) {
164  return false;
165  }
166 
167  parser.get_long(); /* Unit of the geometry data */
168  parser.get_double(); /* Width */
169  parser.get_double(); /* Length */
170  parser.get_double(); /* Height */
171  factor *= parser.get_double(); /* Ballast factor */
172  factor *= parser.get_double(); /* Ballast-Lamp Photometric factor */
173  parser.get_double(); /* Input Watts */
174 
175  /* Intensity values in IES files are specified in candela (lumen/sr), a photometric quantity.
176  * Cycles expects radiometric quantities, though, which requires a conversion.
177  * However, the Luminous efficacy (ratio of lumens per Watt) depends on the spectral distribution
178  * of the light source since lumens take human perception into account.
179  * Since this spectral distribution is not known from the IES file, a typical one must be
180  * assumed. The D65 standard illuminant has a Luminous efficacy of 177.83, which is used here to
181  * convert to Watt/sr. A more advanced approach would be to add a Blackbody Temperature input to
182  * the node and numerically integrate the Luminous efficacy from the resulting spectral
183  * distribution. Also, the Watt/sr value must be multiplied by 4*pi to get the Watt value that
184  * Cycles expects for lamp strength. Therefore, the conversion here uses 4*pi/177.83 as a Candela
185  * to Watt factor.
186  */
187  factor *= 0.0706650768394;
188 
189  v_angles.reserve(v_angles_num);
190  for (int i = 0; i < v_angles_num; i++) {
191  v_angles.push_back((float)parser.get_double());
192  }
193 
194  h_angles.reserve(h_angles_num);
195  for (int i = 0; i < h_angles_num; i++) {
196  h_angles.push_back((float)parser.get_double());
197  }
198 
199  intensity.resize(h_angles_num);
200  for (int i = 0; i < h_angles_num; i++) {
201  intensity[i].reserve(v_angles_num);
202  for (int j = 0; j < v_angles_num; j++) {
203  intensity[i].push_back((float)(factor * parser.get_double()));
204  }
205  }
206 
207  return !parser.eof();
208 }
209 
211 {
212  vector<vector<float>> newintensity;
213  newintensity.resize(v_angles.size());
214  for (int i = 0; i < v_angles.size(); i++) {
215  newintensity[i].reserve(h_angles.size());
216  for (int j = 0; j < h_angles.size(); j++) {
217  newintensity[i].push_back(intensity[j][i]);
218  }
219  }
220  intensity.swap(newintensity);
221  h_angles.swap(v_angles);
222 
223  float h_first = h_angles[0], h_last = h_angles[h_angles.size() - 1];
224  if (h_last != 90.0f) {
225  return false;
226  }
227 
228  if (h_first == 0.0f) {
229  /* The range in the file corresponds to 90°-180°, we need to mirror that to get the
230  * full 180° range. */
231  vector<float> new_h_angles;
232  vector<vector<float>> new_intensity;
233  int hnum = h_angles.size();
234  new_h_angles.reserve(2 * hnum - 1);
235  new_intensity.reserve(2 * hnum - 1);
236  for (int i = hnum - 1; i > 0; i--) {
237  new_h_angles.push_back(90.0f - h_angles[i]);
238  new_intensity.push_back(intensity[i]);
239  }
240  for (int i = 0; i < hnum; i++) {
241  new_h_angles.push_back(90.0f + h_angles[i]);
242  new_intensity.push_back(intensity[i]);
243  }
244  h_angles.swap(new_h_angles);
245  intensity.swap(new_intensity);
246  }
247  else if (h_first == -90.0f) {
248  /* We have full 180° coverage, so just shift to match the angle range convention. */
249  for (int i = 0; i < h_angles.size(); i++) {
250  h_angles[i] += 90.0f;
251  }
252  }
253  /* To get correct results with the cubic interpolation in the kernel, the horizontal range
254  * has to cover all 360°. Therefore, we copy the 0° entry to 360° to ensure full coverage
255  * and seamless interpolation. */
256  h_angles.push_back(360.0f);
257  intensity.push_back(intensity[0]);
258 
259  float v_first = v_angles[0], v_last = v_angles[v_angles.size() - 1];
260  if (v_last != 90.0f) {
261  return false;
262  }
263 
264  if (v_first == 0.0f) {
265  /* The range in the file corresponds to 90°-180°, we need to mirror that to get the
266  * full 180° range. */
267  vector<float> new_v_angles;
268  int hnum = h_angles.size();
269  int vnum = v_angles.size();
270  new_v_angles.reserve(2 * vnum - 1);
271  for (int i = vnum - 1; i > 0; i--) {
272  new_v_angles.push_back(90.0f - v_angles[i]);
273  }
274  for (int i = 0; i < vnum; i++) {
275  new_v_angles.push_back(90.0f + v_angles[i]);
276  }
277  for (int i = 0; i < hnum; i++) {
278  vector<float> new_intensity;
279  new_intensity.reserve(2 * vnum - 1);
280  for (int j = vnum - 2; j >= 0; j--) {
281  new_intensity.push_back(intensity[i][j]);
282  }
283  new_intensity.insert(new_intensity.end(), intensity[i].begin(), intensity[i].end());
284  intensity[i].swap(new_intensity);
285  }
286  v_angles.swap(new_v_angles);
287  }
288  else if (v_first == -90.0f) {
289  /* We have full 180° coverage, so just shift to match the angle range convention. */
290  for (int i = 0; i < v_angles.size(); i++) {
291  v_angles[i] += 90.0f;
292  }
293  }
294 
295  return true;
296 }
297 
299 {
300  if (h_angles[0] == 90.0f) {
301  /* Some files are stored from 90° to 270°, so we just rotate them to the regular 0°-180° range
302  * here. */
303  for (int i = 0; i < h_angles.size(); i++) {
304  h_angles[i] -= 90.0f;
305  }
306  }
307 
308  if (h_angles[0] != 0.0f) {
309  return false;
310  }
311 
312  if (h_angles.size() == 1) {
313  h_angles.push_back(360.0f);
314  intensity.push_back(intensity[0]);
315  }
316 
317  if (h_angles[h_angles.size() - 1] == 90.0f) {
318  /* Only one quadrant is defined, so we need to mirror twice (from one to two, then to four).
319  * Since the two->four mirroring step might also be required if we get an input of two
320  * quadrants, we only do the first mirror here and later do the second mirror in either case.
321  */
322  int hnum = h_angles.size();
323  for (int i = hnum - 2; i >= 0; i--) {
324  h_angles.push_back(180.0f - h_angles[i]);
325  intensity.push_back(intensity[i]);
326  }
327  }
328 
329  if (h_angles[h_angles.size() - 1] == 180.0f) {
330  /* Mirror half to the full range. */
331  int hnum = h_angles.size();
332  for (int i = hnum - 2; i >= 0; i--) {
333  h_angles.push_back(360.0f - h_angles[i]);
334  intensity.push_back(intensity[i]);
335  }
336  }
337 
338  /* Some files skip the 360° entry (contrary to standard) because it's supposed to be identical to
339  * the 0° entry. If the file has a discernible order in its spacing, just fix this. */
340  if (h_angles[h_angles.size() - 1] != 360.0f) {
341  int hnum = h_angles.size();
342  float last_step = h_angles[hnum - 1] - h_angles[hnum - 2];
343  float first_step = h_angles[1] - h_angles[0];
344  float difference = 360.0f - h_angles[hnum - 1];
345  if (last_step == difference || first_step == difference) {
346  h_angles.push_back(360.0f);
347  intensity.push_back(intensity[0]);
348  }
349  else {
350  return false;
351  }
352  }
353 
354  float v_first = v_angles[0], v_last = v_angles[v_angles.size() - 1];
355  if (v_first == 90.0f) {
356  if (v_last == 180.0f) {
357  /* Flip to ensure that vertical angles always start at 0°. */
358  for (int i = 0; i < v_angles.size(); i++) {
359  v_angles[i] = 180.0f - v_angles[i];
360  }
361  }
362  else {
363  return false;
364  }
365  }
366  else if (v_first != 0.0f) {
367  return false;
368  }
369 
370  return true;
371 }
372 
374 {
375  if (h_angles.size() == 0 || v_angles.size() == 0) {
376  return false;
377  }
378 
379  if (type == TYPE_B) {
380  if (!process_type_b()) {
381  return false;
382  }
383  }
384  else {
385  assert(type == TYPE_C);
386  if (!process_type_c()) {
387  return false;
388  }
389  }
390 
391  assert(v_angles[0] == 0.0f);
392  assert(h_angles[0] == 0.0f);
393  assert(h_angles[h_angles.size() - 1] == 360.0f);
394 
395  /* Convert from deg to rad. */
396  for (int i = 0; i < v_angles.size(); i++) {
397  v_angles[i] *= M_PI_F / 180.f;
398  }
399  for (int i = 0; i < h_angles.size(); i++) {
400  h_angles[i] *= M_PI_F / 180.f;
401  }
402 
403  return true;
404 }
405 
407 {
408  clear();
409 }
410 
void pack(float *data)
Definition: util_ies.cpp:58
bool process_type_c()
Definition: util_ies.cpp:298
bool process()
Definition: util_ies.cpp:373
enum IESFile::IESType type
vector< vector< float > > intensity
Definition: util_ies.h:51
vector< float > v_angles
Definition: util_ies.h:48
vector< float > h_angles
Definition: util_ies.h:48
bool load(const string &ies)
Definition: util_ies.cpp:33
~IESFile()
Definition: util_ies.cpp:406
bool parse(const string &ies)
Definition: util_ies.cpp:121
int packed_size()
Definition: util_ies.cpp:50
@ TYPE_C
Definition: util_ies.h:54
@ TYPE_B
Definition: util_ies.h:54
bool process_type_b()
Definition: util_ies.cpp:210
void clear()
Definition: util_ies.cpp:43
vector< char > text
Definition: util_ies.cpp:78
char * data
Definition: util_ies.cpp:79
double get_double()
Definition: util_ies.cpp:92
long get_long()
Definition: util_ies.cpp:106
IESTextParser(const string &str)
Definition: util_ies.cpp:81
bool eof()
Definition: util_ies.cpp:87
#define str(s)
#define CCL_NAMESPACE_END
#define M_PI_F
Definition: util_math.h:43
ccl_device_inline float __int_as_float(int i)
Definition: util_math.h:212