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