RawSpeed
fast raw decoding library
Loading...
Searching...
No Matches
Camera.cpp
Go to the documentation of this file.
1/*
2 RawSpeed - RAW file decoder.
3
4 Copyright (C) 2009-2014 Klaus Post
5 Copyright (C) 2017 Axel Waggershauser
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20*/
21
22#include "metadata/Camera.h"
23#include "adt/Point.h"
27#include <cassert>
28#include <cstdint>
29#include <map>
30#include <string>
31#include <vector>
32
33#ifdef HAVE_PUGIXML
34#include "adt/NotARational.h"
35#include "adt/Optional.h"
36#include "common/Common.h"
37#include <algorithm>
38#include <cctype>
39#include <cstdio>
40#include <pugixml.hpp>
41#include <string_view>
42
43using pugi::xml_node;
44#endif
45
46using std::vector;
47
48using std::map;
49
50namespace rawspeed {
51
52#ifdef HAVE_PUGIXML
53Camera::Camera(const pugi::xml_node& camera) {
54 make = canonical_make = camera.attribute("make").as_string();
55 if (make.empty())
56 ThrowCME(R"("make" attribute not found.)");
57
59 camera.attribute("model").as_string();
60 // chdk cameras seem to have an empty model?
61 if (!camera.attribute("model")) // (model.empty())
62 ThrowCME(R"("model" attribute not found.)");
63
64 canonical_id = make + " " + model;
65
66 supportStatus = [&camera]() {
67 const std::string_view v = camera.attribute("supported").as_string("yes");
68 using enum Camera::SupportStatus;
69 if (v == "yes")
70 return Supported;
71 if (v == "no")
72 return Unsupported;
73 if (v == "no-samples")
74 return SupportedNoSamples;
75 if (v == "unknown")
76 return Unknown;
77 if (v == "unknown-no-samples")
78 return UnknownNoSamples;
79 ThrowCME("Attribute 'supported' has unknown value.");
80 }();
81 mode = camera.attribute("mode").as_string("");
82 decoderVersion = camera.attribute("decoder_version").as_int(0);
83
84 for (xml_node c : camera.children()) {
85 parseCameraChild(c);
86 }
87}
88#endif
89
90Camera::Camera(const Camera* camera, uint32_t alias_num) {
91 if (alias_num >= camera->aliases.size())
92 ThrowCME("Internal error, alias number out of range specified.");
93
94 *this = *camera;
95 model = camera->aliases[alias_num];
96 canonical_alias = camera->canonical_aliases[alias_num];
97 aliases.clear();
98 canonical_aliases.clear();
99}
100
101#ifdef HAVE_PUGIXML
102
103namespace {
104
105std::string name(const xml_node& a) { return a.name(); }
106
107Optional<CFAColor> getAsCFAColor(char c) {
108 switch (c) {
109 using enum CFAColor;
110 case 'g':
111 return GREEN;
112 case 'r':
113 return RED;
114 case 'b':
115 return BLUE;
116 case 'f':
117 return FUJI_GREEN;
118 case 'c':
119 return CYAN;
120 case 'm':
121 return MAGENTA;
122 case 'y':
123 return YELLOW;
124 default:
125 return std::nullopt;
126 }
127}
128
129Optional<CFAColor> getAsCFAColor(std::string_view c) {
130 using enum CFAColor;
131 if (c == "GREEN")
132 return GREEN;
133 if (c == "RED")
134 return RED;
135 if (c == "BLUE")
136 return BLUE;
137 if (c == "FUJI_GREEN")
138 return FUJI_GREEN;
139 if (c == "CYAN")
140 return CYAN;
141 if (c == "MAGENTA")
142 return MAGENTA;
143 if (c == "YELLOW")
144 return YELLOW;
145 return std::nullopt;
146}
147
148} // namespace
149
150void Camera::parseColorRow(const xml_node& c) {
151 if (name(c) != "ColorRow")
152 ThrowCME("Not an ColorRow node!");
153
154 int y = c.attribute("y").as_int(-1);
155 if (y < 0 || y >= cfa.getSize().y) {
156 ThrowCME("Invalid y coordinate in CFA array of camera %s %s", make.c_str(),
157 model.c_str());
158 }
159 std::string key = c.child_value();
160 if (static_cast<int>(key.size()) != cfa.getSize().x) {
161 ThrowCME("Invalid number of colors in definition for row %d in "
162 "camera %s %s. Expected %d, found %zu.",
163 y, make.c_str(), model.c_str(), cfa.getSize().x, key.size());
164 }
165 for (size_t x = 0; x < key.size(); ++x) {
166 auto c1 = key[x];
167
168 auto c2 = getAsCFAColor(static_cast<char>(tolower(c1)));
169 if (!c2)
170 ThrowCME("Invalid color in CFA array of camera %s %s: %c", make.c_str(),
171 model.c_str(), c1);
172
173 cfa.setColorAt(iPoint2D(static_cast<int>(x), y), *c2);
174 }
175}
176
177void Camera::parseColor(const xml_node& c) {
178 if (name(c) != "Color")
179 ThrowCME("Not an Color node!");
180
181 int x = c.attribute("x").as_int(-1);
182 if (x < 0 || x >= cfa.getSize().x) {
183 ThrowCME("Invalid x coordinate in CFA array of camera %s %s", make.c_str(),
184 model.c_str());
185 }
186
187 int y = c.attribute("y").as_int(-1);
188 if (y < 0 || y >= cfa.getSize().y) {
189 ThrowCME("Invalid y coordinate in CFA array of camera %s %s", make.c_str(),
190 model.c_str());
191 }
192
193 const auto* c1 = c.child_value();
194
195 auto c2 = getAsCFAColor(c1);
196 if (!c2)
197 ThrowCME("Invalid color in CFA array of camera %s %s: %s", make.c_str(),
198 model.c_str(), c1);
199
200 cfa.setColorAt(iPoint2D(x, y), *c2);
201}
202
203void Camera::parseCFA(const xml_node& cur) {
204 if (name(cur) != "CFA" && name(cur) != "CFA2")
205 ThrowCME("Not an CFA/CFA2 node!");
206
207 cfa.setSize(iPoint2D(cur.attribute("width").as_int(0),
208 cur.attribute("height").as_int(0)));
209 for (xml_node c : cur.children()) {
210 if (name(c) == "ColorRow") {
211 parseColorRow(c);
212 } else if (name(c) == "Color") {
213 parseColor(c);
214 }
215 }
216}
217
218void Camera::parseCrop(const xml_node& cur) {
219 if (name(cur) != "Crop")
220 ThrowCME("Not an Crop node!");
221
222 const auto widthAttr = cur.attribute("width");
223 const auto heightAttr = cur.attribute("height");
224 const auto xAttr = cur.attribute("x");
225 const auto yAttr = cur.attribute("y");
226
227 cropSize.x = widthAttr.as_int(0);
228 cropSize.y = heightAttr.as_int(0);
229 cropPos.x = xAttr.as_int(0);
230 cropPos.y = yAttr.as_int(0);
231
232 cropAvailable = !(widthAttr.empty() && heightAttr.empty() && xAttr.empty() &&
233 yAttr.empty());
234
235 if (cropPos.x < 0)
236 ThrowCME("Negative X axis crop specified in camera %s %s", make.c_str(),
237 model.c_str());
238 if (cropPos.y < 0)
239 ThrowCME("Negative Y axis crop specified in camera %s %s", make.c_str(),
240 model.c_str());
241}
242
243void Camera::parseBlackAreas(const xml_node& cur) {
244
245 if (name(cur) != "BlackAreas")
246 ThrowCME("Not an BlackAreas node!");
247
248 for (xml_node c : cur.children()) {
249 if (name(c) == "Vertical") {
250 int x = c.attribute("x").as_int(-1);
251 if (x < 0) {
252 ThrowCME(
253 "Invalid x coordinate in vertical BlackArea of in camera %s %s",
254 make.c_str(), model.c_str());
255 }
256
257 int w = c.attribute("width").as_int(-1);
258 if (w < 0) {
259 ThrowCME("Invalid width in vertical BlackArea of in camera %s %s",
260 make.c_str(), model.c_str());
261 }
262
263 blackAreas.emplace_back(x, w, true);
264
265 } else if (name(c) == "Horizontal") {
266
267 int y = c.attribute("y").as_int(-1);
268 if (y < 0) {
269 ThrowCME("Invalid y coordinate in horizontal BlackArea of camera %s %s",
270 make.c_str(), model.c_str());
271 }
272
273 int h = c.attribute("height").as_int(-1);
274 if (h < 0) {
275 ThrowCME("Invalid height in horizontal BlackArea of camera %s %s",
276 make.c_str(), model.c_str());
277 }
278
279 blackAreas.emplace_back(y, h, false);
280 }
281 }
282}
283
284void Camera::parseAliases(const xml_node& cur) {
285 if (name(cur) != "Aliases")
286 ThrowCME("Not an Aliases node!");
287
288 for (xml_node c : cur.children("Alias")) {
289 aliases.emplace_back(c.child_value());
290 canonical_aliases.emplace_back(
291 c.attribute("id").as_string(c.child_value()));
292 }
293}
294
295void Camera::parseHints(const xml_node& cur) {
296 if (name(cur) != "Hints")
297 ThrowCME("Not an Hints node!");
298
299 for (xml_node c : cur.children("Hint")) {
300 std::string name = c.attribute("name").as_string();
301 if (name.empty())
302 ThrowCME("Could not find name for hint for %s %s camera.", make.c_str(),
303 model.c_str());
304
305 std::string value = c.attribute("value").as_string();
306
307 hints.add(name, value);
308 }
309}
310
311void Camera::parseID(const xml_node& cur) {
312 if (name(cur) != "ID")
313 ThrowCME("Not an ID node!");
314
315 canonical_make = cur.attribute("make").as_string();
316 if (canonical_make.empty())
317 ThrowCME("Could not find make for ID for %s %s camera.", make.c_str(),
318 model.c_str());
319
320 canonical_alias = canonical_model = cur.attribute("model").as_string();
321 if (canonical_model.empty())
322 ThrowCME("Could not find model for ID for %s %s camera.", make.c_str(),
323 model.c_str());
324
325 canonical_id = cur.child_value();
326}
327
328void Camera::parseSensor(const xml_node& cur) {
329 if (name(cur) != "Sensor")
330 ThrowCME("Not an Sensor node!");
331
332 auto stringToListOfInts = [&cur](const char* attribute) {
333 vector<int> ret;
334 for (const std::string& s :
335 splitString(cur.attribute(attribute).as_string()))
336 ret.push_back(stoi(s));
337 return ret;
338 };
339
340 int min_iso = cur.attribute("iso_min").as_int(0);
341 int max_iso = cur.attribute("iso_max").as_int(0);
342 int black = cur.attribute("black").as_int(-1);
343 int white = cur.attribute("white").as_int(65536);
344
345 vector<int> black_colors = stringToListOfInts("black_colors");
346 vector<int> iso_list = stringToListOfInts("iso_list");
347 if (!iso_list.empty()) {
348 for (int iso : iso_list) {
349 sensorInfo.emplace_back(black, white, iso, iso, black_colors);
350 }
351 } else {
352 sensorInfo.emplace_back(black, white, min_iso, max_iso, black_colors);
353 }
354}
355
356void Camera::parseColorMatrix(const xml_node& cur) {
357 if (name(cur) != "ColorMatrix")
358 ThrowCME("Not an ColorMatrix node!");
359
360 unsigned planes = cur.attribute("planes").as_uint(~0U);
361 if (planes == ~0U)
362 ThrowCME("Color matrix has unknown number of planes!");
363
364 static constexpr int NumColsPerPlane = 3;
365 color_matrix.resize(NumColsPerPlane * planes, NotARational<int>(0, 0));
366
367 for (xml_node ColorMatrixRow : cur.children("ColorMatrixRow")) {
368 if (name(ColorMatrixRow) != "ColorMatrixRow")
369 ThrowCME("Not an ColorMatrixRow node!");
370
371 auto plane = ColorMatrixRow.attribute("plane").as_uint(~0U);
372 if (plane == ~0U || plane >= planes)
373 ThrowCME("Color matrix row is for unknown plane!");
374
375 const std::vector<std::string> ColsOfRow =
376 splitString(ColorMatrixRow.text().as_string());
377
378 if (ColsOfRow.size() != NumColsPerPlane)
379 ThrowCME("Color matrix row has incorrect number of columns!");
380
381 std::transform(ColsOfRow.begin(), ColsOfRow.end(),
382 color_matrix.begin() + NumColsPerPlane * plane,
383 [](const std::string& Col) -> NotARational<int> {
384 return {std::stoi(Col), 10'000};
385 });
386 }
387}
388
389void Camera::parseColorMatrices(const xml_node& cur) {
390 if (name(cur) != "ColorMatrices")
391 ThrowCME("Not an ColorMatrices node!");
392
393 for (xml_node ColorMatrix : cur.children("ColorMatrix"))
394 parseColorMatrix(ColorMatrix);
395}
396
397void Camera::parseCameraChild(const xml_node& cur) {
398 if (name(cur) == "CFA" || name(cur) == "CFA2") {
399 parseCFA(cur);
400 } else if (name(cur) == "Crop") {
401 parseCrop(cur);
402 } else if (name(cur) == "BlackAreas") {
403 parseBlackAreas(cur);
404 } else if (name(cur) == "Aliases") {
405 parseAliases(cur);
406 } else if (name(cur) == "Hints") {
407 parseHints(cur);
408 } else if (name(cur) == "ID") {
409 parseID(cur);
410 } else if (name(cur) == "Sensor") {
411 parseSensor(cur);
412 } else if (name(cur) == "ColorMatrices") {
413 parseColorMatrices(cur);
414 }
415}
416#endif
417
419 if (sensorInfo.empty())
420 return nullptr;
421
422 // If only one, just return that
423 if (sensorInfo.size() == 1)
424 return &sensorInfo.front();
425
426 vector<const CameraSensorInfo*> candidates;
427 for (const auto& i : sensorInfo) {
428 if (i.isIsoWithin(iso))
429 candidates.push_back(&i);
430 }
431 assert(!candidates.empty());
432
433 if (candidates.size() == 1)
434 return candidates.front();
435
436 for (const auto* i : candidates) {
437 if (!i->isDefault())
438 return i;
439 }
440
441 // Several defaults??? Just return first
442 return candidates.front();
443}
444
445} // namespace rawspeed
#define ThrowCME(...)
#define s
assert(dim.area() >=area)
dim y
Definition Common.cpp:51
dim x
Definition Common.cpp:50
Camera(const Camera *camera, uint32_t alias_num)
Definition Camera.cpp:90
std::string canonical_id
Definition Camera.h:100
std::string model
Definition Camera.h:95
std::string canonical_alias
Definition Camera.h:99
iPoint2D cropPos
Definition Camera.h:106
std::string canonical_model
Definition Camera.h:98
std::string mode
Definition Camera.h:96
iPoint2D cropSize
Definition Camera.h:105
std::vector< CameraSensorInfo > sensorInfo
Definition Camera.h:108
bool cropAvailable
Definition Camera.h:117
std::vector< std::string > canonical_aliases
Definition Camera.h:102
std::string canonical_make
Definition Camera.h:97
std::vector< BlackArea > blackAreas
Definition Camera.h:107
const CameraSensorInfo * getSensorInfo(int iso) const
Definition Camera.cpp:418
std::vector< std::string > aliases
Definition Camera.h:101
SupportStatus supportStatus
Definition Camera.h:104
std::vector< NotARational< int > > color_matrix
Definition Camera.h:111
ColorFilterArray cfa
Definition Camera.h:103
std::string make
Definition Camera.h:94
std::vector< std::string > splitString(const std::string &input, char c=' ')
Definition Common.h:178