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 "common/Common.h"                    // for splitString
24 #include "common/Point.h"                     // for iPoint2D
25 #include "metadata/CameraMetadataException.h" // for ThrowCME
26 #include <algorithm>                          // for max
27 #include <cctype>                             // for tolower
28 #include <cstdio>                             // for size_t
29 #include <map>                                // for map
30 #include <stdexcept>                          // for out_of_range
31 #include <string>                             // for string, operator==
32 #include <vector>                             // for vector
33 
34 #ifdef HAVE_PUGIXML
35 #include <pugixml.hpp> // for xml_node, xml_attribute
36 using pugi::xml_node;
37 #endif
38 
39 using std::vector;
40 using std::string;
41 using std::map;
42 
43 namespace rawspeed {
44 
45 #ifdef HAVE_PUGIXML
Camera(const pugi::xml_node & camera)46 Camera::Camera(const pugi::xml_node& camera) : cfa(iPoint2D(0, 0)) {
47   make = canonical_make = camera.attribute("make").as_string();
48   if (make.empty())
49     ThrowCME(R"("make" attribute not found.)");
50 
51   model = canonical_model = canonical_alias = camera.attribute("model").as_string();
52   // chdk cameras seem to have an empty model?
53   if (!camera.attribute("model")) // (model.empty())
54     ThrowCME(R"("model" attribute not found.)");
55 
56   canonical_id = make + " " + model;
57 
58   supported = camera.attribute("supported").as_string("yes") == string("yes");
59   mode = camera.attribute("mode").as_string("");
60   decoderVersion = camera.attribute("decoder_version").as_int(0);
61 
62   for (xml_node c : camera.children()) {
63     parseCameraChild(c);
64   }
65 }
66 #endif
67 
Camera(const Camera * camera,uint32_t alias_num)68 Camera::Camera(const Camera* camera, uint32_t alias_num) : cfa(iPoint2D(0, 0)) {
69   if (alias_num >= camera->aliases.size())
70     ThrowCME("Internal error, alias number out of range specified.");
71 
72   *this = *camera;
73   model = camera->aliases[alias_num];
74   canonical_alias = camera->canonical_aliases[alias_num];
75   aliases.clear();
76   canonical_aliases.clear();
77 }
78 
79 #ifdef HAVE_PUGIXML
name(const xml_node & a)80 static string name(const xml_node &a) {
81   return string(a.name());
82 }
83 #endif
84 
85 const map<char, CFAColor> Camera::char2enum = {
86     {'g', CFA_GREEN},      {'r', CFA_RED},  {'b', CFA_BLUE},
87     {'f', CFA_FUJI_GREEN}, {'c', CFA_CYAN}, {'m', CFA_MAGENTA},
88     {'y', CFA_YELLOW},
89 };
90 
91 const map<string, CFAColor> Camera::str2enum = {
92     {"GREEN", CFA_GREEN},   {"RED", CFA_RED},
93     {"BLUE", CFA_BLUE},     {"FUJI_GREEN", CFA_FUJI_GREEN},
94     {"CYAN", CFA_CYAN},     {"MAGENTA", CFA_MAGENTA},
95     {"YELLOW", CFA_YELLOW},
96 };
97 
98 #ifdef HAVE_PUGIXML
parseCFA(const xml_node & cur)99 void Camera::parseCFA(const xml_node &cur) {
100   if (name(cur) != "CFA" && name(cur) != "CFA2")
101     ThrowCME("Not an CFA/CFA2 node!");
102 
103   cfa.setSize(iPoint2D(cur.attribute("width").as_int(0),
104                        cur.attribute("height").as_int(0)));
105   for (xml_node c : cur.children()) {
106       if (name(c) == "ColorRow") {
107         int y = c.attribute("y").as_int(-1);
108         if (y < 0 || y >= cfa.getSize().y) {
109           ThrowCME("Invalid y coordinate in CFA array of camera %s %s",
110                    make.c_str(), model.c_str());
111         }
112         string key = c.child_value();
113         if (static_cast<int>(key.size()) != cfa.getSize().x) {
114           ThrowCME("Invalid number of colors in definition for row %d in "
115                    "camera %s %s. Expected %d, found %zu.",
116                    y, make.c_str(), model.c_str(), cfa.getSize().x, key.size());
117         }
118         for (size_t x = 0; x < key.size(); ++x) {
119           auto c1 = key[x];
120           CFAColor c2;
121 
122           try {
123             c2 = char2enum.at(static_cast<char>(tolower(c1)));
124           } catch (std::out_of_range&) {
125             ThrowCME("Invalid color in CFA array of camera %s %s: %c",
126                      make.c_str(), model.c_str(), c1);
127           }
128 
129           cfa.setColorAt(iPoint2D(static_cast<int>(x), y), c2);
130         }
131       } else if (name(c) == "Color") {
132         int x = c.attribute("x").as_int(-1);
133         if (x < 0 || x >= cfa.getSize().x) {
134           ThrowCME("Invalid x coordinate in CFA array of camera %s %s",
135                    make.c_str(), model.c_str());
136         }
137 
138         int y = c.attribute("y").as_int(-1);
139         if (y < 0 || y >= cfa.getSize().y) {
140           ThrowCME("Invalid y coordinate in CFA array of camera %s %s",
141                    make.c_str(), model.c_str());
142         }
143 
144         const auto* c1 = c.child_value();
145         CFAColor c2;
146 
147         try {
148           c2 = str2enum.at(c1);
149         } catch (std::out_of_range&) {
150           ThrowCME("Invalid color in CFA array of camera %s %s: %s",
151                    make.c_str(), model.c_str(), c1);
152         }
153 
154         cfa.setColorAt(iPoint2D(x, y), c2);
155       }
156   }
157 }
158 
parseCrop(const xml_node & cur)159 void Camera::parseCrop(const xml_node &cur) {
160   if (name(cur) != "Crop")
161     ThrowCME("Not an Crop node!");
162 
163   cropSize.x = cur.attribute("width").as_int(0);
164   cropSize.y = cur.attribute("height").as_int(0);
165   cropPos.x = cur.attribute("x").as_int(0);
166   cropPos.y = cur.attribute("y").as_int(0);
167 
168   if (cropPos.x < 0)
169     ThrowCME("Negative X axis crop specified in camera %s %s", make.c_str(),
170              model.c_str());
171   if (cropPos.y < 0)
172     ThrowCME("Negative Y axis crop specified in camera %s %s", make.c_str(),
173              model.c_str());
174 }
175 
parseBlackAreas(const xml_node & cur)176 void Camera::parseBlackAreas(const xml_node &cur) {
177 
178   if (name(cur) != "BlackAreas")
179     ThrowCME("Not an BlackAreas node!");
180 
181   for (xml_node c : cur.children()) {
182     if (name(c) == "Vertical") {
183       int x = c.attribute("x").as_int(-1);
184       if (x < 0) {
185         ThrowCME(
186             "Invalid x coordinate in vertical BlackArea of in camera %s %s",
187             make.c_str(), model.c_str());
188       }
189 
190       int w = c.attribute("width").as_int(-1);
191       if (w < 0) {
192         ThrowCME("Invalid width in vertical BlackArea of in camera %s %s",
193                  make.c_str(), model.c_str());
194       }
195 
196       blackAreas.emplace_back(x, w, true);
197 
198     } else if (name(c) == "Horizontal") {
199 
200       int y = c.attribute("y").as_int(-1);
201       if (y < 0) {
202         ThrowCME("Invalid y coordinate in horizontal BlackArea of camera %s %s",
203                  make.c_str(), model.c_str());
204       }
205 
206       int h = c.attribute("height").as_int(-1);
207       if (h < 0) {
208         ThrowCME("Invalid height in horizontal BlackArea of camera %s %s",
209                  make.c_str(), model.c_str());
210       }
211 
212       blackAreas.emplace_back(y, h, false);
213     }
214   }
215 }
216 
parseAliases(const xml_node & cur)217 void Camera::parseAliases(const xml_node &cur) {
218   if (name(cur) != "Aliases")
219     ThrowCME("Not an Aliases node!");
220 
221   for (xml_node c : cur.children("Alias")) {
222     aliases.emplace_back(c.child_value());
223     canonical_aliases.emplace_back(
224         c.attribute("id").as_string(c.child_value()));
225   }
226 }
227 
parseHints(const xml_node & cur)228 void Camera::parseHints(const xml_node &cur) {
229   if (name(cur) != "Hints")
230     ThrowCME("Not an Hints node!");
231 
232   for (xml_node c : cur.children("Hint")) {
233     string name = c.attribute("name").as_string();
234     if (name.empty())
235       ThrowCME("Could not find name for hint for %s %s camera.", make.c_str(),
236                model.c_str());
237 
238     string value = c.attribute("value").as_string();
239 
240     hints.add(name, value);
241   }
242 }
243 
parseID(const xml_node & cur)244 void Camera::parseID(const xml_node &cur) {
245   if (name(cur) != "ID")
246     ThrowCME("Not an ID node!");
247 
248   canonical_make = cur.attribute("make").as_string();
249   if (canonical_make.empty())
250     ThrowCME("Could not find make for ID for %s %s camera.", make.c_str(),
251              model.c_str());
252 
253   canonical_alias = canonical_model = cur.attribute("model").as_string();
254   if (canonical_model.empty())
255     ThrowCME("Could not find model for ID for %s %s camera.", make.c_str(),
256              model.c_str());
257 
258   canonical_id = cur.child_value();
259 }
260 
parseSensor(const xml_node & cur)261 void Camera::parseSensor(const xml_node &cur) {
262   if (name(cur) != "Sensor")
263     ThrowCME("Not an Sensor node!");
264 
265   auto stringToListOfInts = [&cur](const char* attribute) {
266     vector<int> ret;
267     for (const string& s : splitString(cur.attribute(attribute).as_string()))
268       ret.push_back(stoi(s));
269     return ret;
270   };
271 
272   int min_iso = cur.attribute("iso_min").as_int(0);
273   int max_iso = cur.attribute("iso_max").as_int(0);
274   int black = cur.attribute("black").as_int(-1);
275   int white = cur.attribute("white").as_int(65536);
276 
277   vector<int> black_colors = stringToListOfInts("black_colors");
278   vector<int> iso_list = stringToListOfInts("iso_list");
279   if (!iso_list.empty()) {
280     for (int iso : iso_list) {
281       sensorInfo.emplace_back(black, white, iso, iso, black_colors);
282     }
283   } else {
284     sensorInfo.emplace_back(black, white, min_iso, max_iso, black_colors);
285   }
286 }
287 
parseCameraChild(const xml_node & cur)288 void Camera::parseCameraChild(const xml_node &cur) {
289   if (name(cur) == "CFA" || name(cur) == "CFA2") {
290     parseCFA(cur);
291   } else if (name(cur) == "Crop") {
292     parseCrop(cur);
293   } else if (name(cur) == "BlackAreas") {
294     parseBlackAreas(cur);
295   } else if (name(cur) == "Aliases") {
296     parseAliases(cur);
297   } else if (name(cur) == "Hints") {
298     parseHints(cur);
299   } else if (name(cur) == "ID") {
300     parseID(cur);
301   } else if (name(cur) == "Sensor") {
302     parseSensor(cur);
303   }
304 }
305 #endif
306 
getSensorInfo(int iso) const307 const CameraSensorInfo* Camera::getSensorInfo(int iso) const {
308   if (sensorInfo.empty()) {
309     ThrowCME("Camera '%s' '%s', mode '%s' has no <Sensor> entries.",
310              make.c_str(), model.c_str(), mode.c_str());
311   }
312 
313   // If only one, just return that
314   if (sensorInfo.size() == 1)
315     return &sensorInfo.front();
316 
317   vector<const CameraSensorInfo*> candidates;
318   for (const auto& i : sensorInfo) {
319     if (i.isIsoWithin(iso))
320       candidates.push_back(&i);
321   }
322 
323   if (candidates.size() == 1)
324     return candidates.front();
325 
326   for (const auto* i : candidates) {
327     if (!i->isDefault())
328       return i;
329   }
330 
331   // Several defaults??? Just return first
332   return candidates.front();
333 }
334 
335 } // namespace rawspeed
336