-
Notifications
You must be signed in to change notification settings - Fork 606
/
Copy pathrawinput.cpp
1550 lines (1390 loc) · 54.5 KB
/
rawinput.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2008-present Contributors to the OpenImageIO project.
// SPDX-License-Identifier: BSD-3-Clause
// https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
#include <algorithm>
#include <ctime> /* time_t, struct tm, gmtime */
#include <iostream>
#include <memory>
#include <OpenImageIO/fmath.h>
#include <OpenImageIO/imageio.h>
#include <OpenImageIO/platform.h>
#include <OpenImageIO/strutil.h>
#include <OpenImageIO/sysutil.h>
#include <OpenImageIO/tiffutils.h>
#if OIIO_GNUC_VERSION || OIIO_CLANG_VERSION >= 50000
// fix warnings in libraw headers: use of auto_ptr
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
#if OIIO_CPLUSPLUS_VERSION >= 17 \
&& ((OIIO_CLANG_VERSION && OIIO_CLANG_VERSION < 110000) \
|| OIIO_APPLE_CLANG_VERSION)
// libraw uses auto_ptr, which is not in C++17 at all for clang, though
// it does seem to be for gcc. So for clang, alias it to unique_ptr.
namespace std {
template<class T> using auto_ptr = unique_ptr<T>;
}
#endif
#include <libraw/libraw.h>
#include <libraw/libraw_version.h>
// This plugin utilises LibRaw:
// http://www.libraw.org/
// Documentation:
// http://www.libraw.org/docs
// Example raw images from many camera models:
// https://www.rawsamples.ch
OIIO_PLUGIN_NAMESPACE_BEGIN
class RawInput final : public ImageInput {
public:
RawInput() {}
virtual ~RawInput() { close(); }
virtual const char* format_name(void) const override { return "raw"; }
virtual int supports(string_view feature) const override
{
return (feature == "exif"
/* not yet? || feature == "iptc"*/);
}
virtual bool open(const std::string& name, ImageSpec& newspec) override;
virtual bool open(const std::string& name, ImageSpec& newspec,
const ImageSpec& config) override;
virtual bool close() override;
virtual bool read_native_scanline(int subimage, int miplevel, int y, int z,
void* data) override;
private:
bool process();
bool m_process = true;
bool m_unpacked = false;
std::unique_ptr<LibRaw> m_processor;
libraw_processed_image_t* m_image = nullptr;
bool m_do_scene_linear_scale = false;
float m_camera_to_scene_linear_scale
= (1.0f / 0.45f); // see open_raw for details
bool m_do_balance_clamped = false;
float m_balanced_max = 1.0;
std::string m_filename;
ImageSpec m_config; // save config requests
std::string m_make;
bool do_unpack();
// Do the actual open. It expects m_filename and m_config to be set.
bool open_raw(bool unpack, const std::string& name,
const ImageSpec& config);
void get_makernotes();
void get_makernotes_canon();
void get_makernotes_nikon();
void get_makernotes_olympus();
void get_makernotes_fuji();
void get_makernotes_kodak();
void get_makernotes_pentax();
void get_makernotes_panasonic();
void get_makernotes_sony();
void get_lensinfo();
void get_shootinginfo();
template<typename T> static bool allval(cspan<T> d, T v = T(0))
{
return std::all_of(d.begin(), d.end(),
[&](const T& a) { return a == v; });
}
static std::string prefixedname(string_view prefix, std::string& name)
{
return prefix.size() ? (std::string(prefix) + ':' + name) : name;
}
void add(string_view prefix, std::string name, int data, bool force = true,
int ignval = 0)
{
if (force || data != ignval)
m_spec.attribute(prefixedname(prefix, name), data);
}
void add(string_view prefix, std::string name, float data,
bool force = true, float ignval = 0)
{
if (force || data != ignval)
m_spec.attribute(prefixedname(prefix, name), data);
}
void add(string_view prefix, std::string name, string_view data,
bool force = true, int /*ignval*/ = 0)
{
if (force || (data.size() && data[0]))
m_spec.attribute(prefixedname(prefix, name), data);
}
void add(string_view prefix, std::string name, unsigned long long data,
bool force = true, unsigned long long ignval = 0)
{
if (force || data != ignval)
m_spec.attribute(prefixedname(prefix, name), TypeDesc::UINT64,
&data);
}
void add(string_view prefix, std::string name, unsigned int data,
bool force = true, int ignval = 0)
{
add(prefix, name, (int)data, force, ignval);
}
void add(string_view prefix, std::string name, unsigned short data,
bool force = true, int ignval = 0)
{
add(prefix, name, (int)data, force, ignval);
}
void add(string_view prefix, std::string name, unsigned char data,
bool force = true, int ignval = 0)
{
add(prefix, name, (int)data, force, ignval);
}
void add(string_view prefix, std::string name, double data,
bool force = true, float ignval = 0)
{
add(prefix, name, float(data), force, ignval);
}
void add(string_view prefix, std::string name, cspan<int> data,
bool force = true, int ignval = 0)
{
if (force || !allval(data, ignval)) {
int size = data.size() > 1 ? data.size() : 0;
m_spec.attribute(prefixedname(prefix, name),
TypeDesc(TypeDesc::INT, size), data.data());
}
}
void add(string_view prefix, std::string name, cspan<short> data,
bool force = true, short ignval = 0)
{
if (force || !allval(data, ignval)) {
int size = data.size() > 1 ? data.size() : 0;
m_spec.attribute(prefixedname(prefix, name),
TypeDesc(TypeDesc::INT16, size), data.data());
}
}
void add(string_view prefix, std::string name, cspan<unsigned short> data,
bool force = true, unsigned short ignval = 0)
{
if (force || !allval(data, ignval)) {
int size = data.size() > 1 ? data.size() : 0;
m_spec.attribute(prefixedname(prefix, name),
TypeDesc(TypeDesc::UINT16, size), data.data());
}
}
void add(string_view prefix, std::string name, cspan<unsigned char> data,
bool force = true, unsigned char ignval = 0)
{
if (force || !allval(data, ignval)) {
int size = data.size() > 1 ? data.size() : 0;
m_spec.attribute(prefixedname(prefix, name),
TypeDesc(TypeDesc::UINT8, size), data.data());
}
}
void add(string_view prefix, std::string name, cspan<float> data,
bool force = true, float ignval = 0)
{
if (force || !allval(data, ignval)) {
int size = data.size() > 1 ? data.size() : 0;
m_spec.attribute(prefixedname(prefix, name),
TypeDesc(TypeDesc::FLOAT, size), data.data());
}
}
void add(string_view prefix, std::string name, cspan<double> data,
bool force = true, float ignval = 0)
{
float* d = OIIO_ALLOCA(float, data.size());
for (auto i = 0; i < data.size(); ++i)
d[i] = data[i];
add(prefix, name, cspan<float>(d, data.size()), force, ignval);
}
};
// Export version number and create function symbols
OIIO_PLUGIN_EXPORTS_BEGIN
OIIO_EXPORT int raw_imageio_version = OIIO_PLUGIN_VERSION;
OIIO_EXPORT const char*
raw_imageio_library_version()
{
return ustring::sprintf("libraw %s", libraw_version()).c_str();
}
OIIO_EXPORT ImageInput*
raw_input_imageio_create()
{
return new RawInput;
}
OIIO_EXPORT const char* raw_input_extensions[]
= { "bay", "bmq", "cr2",
#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 20, 0)
"cr3",
#endif
"crw", "cs1", "dc2", "dcr", "dng", "erf", "fff", "hdr", "k25",
"kdc", "mdc", "mos", "mrw", "nef", "orf", "pef", "pxn", "raf",
"raw", "rdc", "sr2", "srf", "x3f", "arw", "3fr", "cine", "ia",
"kc2", "mef", "nrw", "qtk", "rw2", "sti", "rwl", "srw", "drf",
"dsc", "ptx", "cap", "iiq", "rwz", "cr3", nullptr };
OIIO_PLUGIN_EXPORTS_END
namespace {
const char*
libraw_filter_to_str(unsigned int filters)
{
// Convert the libraw filter pattern description
// into a slightly more human readable string
// LibRaw/internal/defines.h:166
switch (filters) {
// CYGM
case 0xe1e4e1e4: return "GMYC";
case 0x1b4e4b1e: return "CYGM";
case 0x1e4b4e1b: return "YCGM";
case 0xb4b4b4b4: return "GMCY";
case 0x1e4e1e4e: return "CYMG";
// RGB
case 0x16161616: return "BGRG";
case 0x61616161: return "GRGB";
case 0x49494949: return "GBGR";
case 0x94949494: return "RGBG";
default: break;
}
return "";
}
} // namespace
bool
RawInput::open(const std::string& name, ImageSpec& newspec)
{
// If user doesn't want to provide any config, just use an empty spec.
ImageSpec config;
return open(name, newspec, config);
}
#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 17, 0)
static void
exif_parser_cb(ImageSpec* spec, int tag, int tifftype, int len,
unsigned int byteorder, LibRaw_abstract_datastream* ifp)
{
// Oy, the data offsets are all going to be relative to the start of the
// stream, not relative to our current position and data block. So we
// need to remember that offset and pass its negative as the
// offset_adjustment to the handler.
size_t streampos = ifp->tell();
// std::cerr << "Stream position " << streampos << "\n";
TypeDesc type = tiff_datatype_to_typedesc(TIFFDataType(tifftype),
size_t(len));
const TagInfo* taginfo = tag_lookup("Exif", tag);
if (!taginfo) {
// Strutil::fprintf (std::cerr, "NO TAGINFO FOR CALLBACK tag=%d (0x%x): tifftype=%d,len=%d (%s), byteorder=0x%x\n",
// tag, tag, tifftype, len, type, byteorder);
return;
}
if (type.size() >= (1 << 20))
return; // sanity check -- too much memory
size_t size = tiff_data_size(TIFFDataType(tifftype)) * len;
std::vector<unsigned char> buf(size);
ifp->read(buf.data(), size, 1);
// debug scaffolding
// Strutil::fprintf (std::cerr, "CALLBACK tag=%s: tifftype=%d,len=%d (%s), byteorder=0x%x\n",
// taginfo->name, tifftype, len, type, byteorder);
// for (int i = 0; i < std::min(16UL,size); ++i) {
// if (buf[i] >= ' ' && buf[i] < 128)
// std::cerr << char(buf[i]);
// Strutil::fprintf (std::cerr, "(%d) ", int(buf[i]));
// }
// std::cerr << "\n";
bool swab = (littleendian() != (byteorder == 0x4949));
if (swab) {
if (type.basetype == TypeDesc::UINT16)
swap_endian((uint16_t*)buf.data(), len);
if (type.basetype == TypeDesc::UINT32)
swap_endian((uint32_t*)buf.data(), len);
}
if (taginfo->handler) {
TIFFDirEntry dir;
dir.tdir_tag = uint16_t(tag);
dir.tdir_type = uint16_t(tifftype);
dir.tdir_count = uint32_t(len);
dir.tdir_offset = 0;
taginfo->handler(*taginfo, dir, buf, *spec, swab, -int(streampos));
// std::cerr << "HANDLED " << taginfo->name << "\n";
return;
}
if (taginfo->tifftype == TIFF_NOTYPE)
return; // skip
if (tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL) {
spec->attribute(taginfo->name, type, buf.data());
return;
}
if (type.basetype == TypeDesc::UINT16) {
spec->attribute(taginfo->name, type, buf.data());
return;
}
if (type.basetype == TypeDesc::UINT32) {
spec->attribute(taginfo->name, type, buf.data());
return;
}
if (type == TypeString) {
spec->attribute(taginfo->name, string_view((char*)buf.data(), size));
return;
}
// Strutil::fprintf (std::cerr, "RAW metadata NOT HANDLED: tag=%s: tifftype=%d,len=%d (%s), byteorder=0x%x\n",
// taginfo->name, tifftype, len, type, byteorder);
}
#endif
bool
RawInput::open(const std::string& name, ImageSpec& newspec,
const ImageSpec& config)
{
m_filename = name;
m_config = config;
// For a fresh open, we are concerned with just reading all the
// metadata quickly, because maybe that's all that will be needed. So
// call open_raw passing unpack=false. This will not read the pixels! We
// will need to close and re-open with unpack=true if and when we need
// the actual pixel values.
bool ok = open_raw(false, m_filename, m_config);
if (ok)
newspec = m_spec;
return ok;
}
bool
RawInput::open_raw(bool unpack, const std::string& name,
const ImageSpec& config)
{
// std::cout << "open_raw " << name << " unpack=" << unpack << "\n";
{
// See https://github.com/OpenImageIO/oiio/issues/2630
// Something inside LibRaw constructor is not thread safe. Use a
// static mutex here to make sure only one thread is constructing a
// LibRaw at a time. Cross fingers and hope all the rest of LibRaw
// is re-entrant.
static std::mutex libraw_ctr_mutex;
std::lock_guard<std::mutex> lock(libraw_ctr_mutex);
m_processor.reset(new LibRaw);
}
// Temp spec for exif parser callback to dump into
ImageSpec exifspec;
#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 17, 0)
m_processor->set_exifparser_handler((exif_parser_callback)exif_parser_cb,
&exifspec);
#endif
// Force flip value if needed. If user_flip is -1, libraw ignores it
m_processor->imgdata.params.user_flip
= config.get_int_attribute("raw:user_flip", -1);
#ifdef _WIN32
// Convert to wide chars, just on Windows.
int ret = m_processor->open_file(Strutil::utf8_to_utf16(name).c_str());
#else
int ret = m_processor->open_file(name.c_str());
#endif
if (ret != LIBRAW_SUCCESS) {
errorf("Could not open file \"%s\", %s", m_filename,
libraw_strerror(ret));
return false;
}
OIIO_ASSERT(!m_unpacked);
if (unpack) {
if ((ret = m_processor->unpack()) != LIBRAW_SUCCESS) {
errorf("Could not unpack \"%s\", %s", m_filename,
libraw_strerror(ret));
return false;
}
m_unpacked = true;
}
m_processor->adjust_sizes_info_only();
// Process image at half size if "raw:half_size" is not 0
m_processor->imgdata.params.half_size
= config.get_int_attribute("raw:half_size", 0);
int div = m_processor->imgdata.params.half_size == 0 ? 1 : 2;
// Set file information
m_spec = ImageSpec(m_processor->imgdata.sizes.iwidth / div,
m_processor->imgdata.sizes.iheight / div,
3, // LibRaw should only give us 3 channels
TypeDesc::UINT16);
// Move the exif attribs we already read into the spec we care about
m_spec.extra_attribs.swap(exifspec.extra_attribs);
// Output 16 bit images
m_processor->imgdata.params.output_bps = 16;
// Disable exposure correction (unless config "raw:auto_bright" == 1)
m_processor->imgdata.params.no_auto_bright
= !config.get_int_attribute("raw:auto_bright", 0);
// Turn off maximum threshold value (unless set to non-zero)
m_processor->imgdata.params.adjust_maximum_thr
= config.get_float_attribute("raw:adjust_maximum_thr", 0.0f);
// Set camera maximum value if "raw:user_sat" is not 0
m_processor->imgdata.params.user_sat
= config.get_int_attribute("raw:user_sat", 0);
{
auto p = config.find_attribute("raw:aber");
if (p && p->type() == TypeDesc(TypeDesc::FLOAT, 2)) {
m_processor->imgdata.params.aber[0] = p->get<float>(0);
m_processor->imgdata.params.aber[2] = p->get<float>(1);
}
if (p && p->type() == TypeDesc(TypeDesc::DOUBLE, 2)) {
m_processor->imgdata.params.aber[0] = p->get<double>(0);
m_processor->imgdata.params.aber[2] = p->get<double>(1);
}
}
// Always disable the camera white-balance setting as it stops
// other modes from working. Instead, we can put the camera white
// balance values into the user mults if desired
m_processor->imgdata.params.use_camera_wb = 0;
if (config.get_int_attribute("raw:use_camera_wb", 1) == 1) {
auto& color = m_processor->imgdata.color;
auto& params = m_processor->imgdata.params;
auto& idata = m_processor->imgdata.idata;
auto is_rgbg_or_bgrg = [&](unsigned int filters) {
std::string filter(libraw_filter_to_str(filters));
return filter == "RGBG" || filter == "BGRG";
};
float norm[4] = { color.cam_mul[0], color.cam_mul[1], color.cam_mul[2],
color.cam_mul[3] };
if (is_rgbg_or_bgrg(idata.filters)) {
// normalize white balance around green
norm[0] /= norm[1];
norm[1] /= norm[1];
norm[2] /= norm[3] > 0 ? norm[3] : norm[1];
norm[3] /= norm[3] > 0 ? norm[3] : norm[1];
}
params.user_mul[0] = norm[0];
params.user_mul[1] = norm[1];
params.user_mul[2] = norm[2];
params.user_mul[3] = norm[3];
} else {
// Set user white balance coefficients.
// Only has effect if "raw:use_camera_wb" is equal to 0,
// i.e. we are not using the camera white balance
auto p = config.find_attribute("raw:user_mul");
if (p && p->type() == TypeDesc(TypeDesc::FLOAT, 4)) {
m_processor->imgdata.params.user_mul[0] = p->get<float>(0);
m_processor->imgdata.params.user_mul[1] = p->get<float>(1);
m_processor->imgdata.params.user_mul[2] = p->get<float>(2);
m_processor->imgdata.params.user_mul[3] = p->get<float>(3);
}
if (p && p->type() == TypeDesc(TypeDesc::DOUBLE, 4)) {
m_processor->imgdata.params.user_mul[0] = p->get<double>(0);
m_processor->imgdata.params.user_mul[1] = p->get<double>(1);
m_processor->imgdata.params.user_mul[2] = p->get<double>(2);
m_processor->imgdata.params.user_mul[3] = p->get<double>(3);
}
}
// Use embedded color profile. Values mean:
// 0: do not use embedded color profile
// 1 (default): use embedded color profile (if present) for DNG files
// (always), for other files only if use_camera_wb is set.
// 3: use embedded color data (if present) regardless of white
// balance setting.
m_processor->imgdata.params.use_camera_matrix
= config.get_int_attribute("raw:use_camera_matrix", 1);
// Check to see if the user has explicitly requested output colorspace
// primaries via a configuration hint "raw:ColorSpace". The default if
// there is no such hint is convert to sRGB, so that if somebody just
// naively reads a raw image and slaps it into a framebuffer for
// display, it will work just like a jpeg. More sophisticated users
// might request a particular color space, like "ACES". Note that a
// request for "sRGB-linear" will give you sRGB primaries with a linear
// response.
std::string cs = config.get_string_attribute("raw:ColorSpace", "sRGB");
if (Strutil::iequals(cs, "raw")) {
// Values straight from the chip
m_processor->imgdata.params.output_color = 0;
m_processor->imgdata.params.gamm[0] = 1.0;
m_processor->imgdata.params.gamm[1] = 1.0;
} else if (Strutil::iequals(cs, "sRGB")) {
// Request explicit sRGB, including usual sRGB response
m_processor->imgdata.params.output_color = 1;
m_processor->imgdata.params.gamm[0] = 1.0 / 2.4;
m_processor->imgdata.params.gamm[1] = 12.92;
} else if (Strutil::iequals(cs, "Adobe")) {
// Request Adobe color space with 2.2 gamma (no linear toe)
m_processor->imgdata.params.output_color = 2;
m_processor->imgdata.params.gamm[0] = 1.0 / 2.2;
m_processor->imgdata.params.gamm[1] = 0.0;
} else if (Strutil::iequals(cs, "Wide")) {
m_processor->imgdata.params.output_color = 3;
m_processor->imgdata.params.gamm[0] = 1.0;
m_processor->imgdata.params.gamm[1] = 1.0;
} else if (Strutil::iequals(cs, "ProPhoto")) {
// ProPhoto by convention has gamma 1.8
m_processor->imgdata.params.output_color = 4;
m_processor->imgdata.params.gamm[0] = 1.8;
m_processor->imgdata.params.gamm[1] = 0.0;
} else if (Strutil::iequals(cs, "XYZ")) {
// XYZ linear
m_processor->imgdata.params.output_color = 5;
m_processor->imgdata.params.gamm[0] = 1.0;
m_processor->imgdata.params.gamm[1] = 1.0;
} else if (Strutil::iequals(cs, "ACES")) {
#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 18, 0)
// ACES linear
m_processor->imgdata.params.output_color = 6;
m_processor->imgdata.params.gamm[0] = 1.0;
m_processor->imgdata.params.gamm[1] = 1.0;
#else
errorfmt("raw:ColorSpace value of \"{}\" is not supported by libRaw {}",
cs, LIBRAW_VERSION_STR);
return false;
#endif
} else if (Strutil::iequals(cs, "DCI-P3")) {
#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 21, 0)
// ACES linear
m_processor->imgdata.params.output_color = 7;
m_processor->imgdata.params.gamm[0] = 1.0;
m_processor->imgdata.params.gamm[1] = 1.0;
#else
errorfmt("raw:ColorSpace value of \"{}\" is not supported by libRaw {}",
cs, LIBRAW_VERSION_STR);
return false;
#endif
} else if (Strutil::iequals(cs, "Rec2020")) {
#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 21, 0)
// ACES linear
m_processor->imgdata.params.output_color = 8;
m_processor->imgdata.params.gamm[0] = 1.0;
m_processor->imgdata.params.gamm[1] = 1.0;
#else
errorfmt("raw:ColorSpace value of \"{}\" is not supported by libRaw {}",
cs, LIBRAW_VERSION_STR);
return false;
#endif
} else if (Strutil::iequals(cs, "sRGB-linear")
|| Strutil::iequals(cs, "linear") /* DEPRECATED */) {
// Request "sRGB" primaries, linear reponse
m_processor->imgdata.params.output_color = 1;
m_processor->imgdata.params.gamm[0] = 1.0;
m_processor->imgdata.params.gamm[1] = 1.0;
} else {
errorf("raw:ColorSpace set to unknown value \"%s\"", cs);
return false;
}
m_spec.attribute("oiio:ColorSpace", cs);
// Exposure adjustment
float exposure = config.get_float_attribute("raw:Exposure", -1.0f);
if (exposure >= 0.0f) {
if (exposure < 0.25f || exposure > 8.0f) {
errorf("raw:Exposure invalid value. range 0.25f - 8.0f");
return false;
}
m_processor->imgdata.params.exp_correc
= 1; // enable exposure correction
m_processor->imgdata.params.exp_shift
= exposure; // set exposure correction
// Set the attribute in the output spec
m_spec.attribute("raw:Exposure", exposure);
}
// Interpolation quality
// note: LibRaw must be compiled with demosaic pack GPL2 to use demosaic
// algorithms 5-9. It must be compiled with demosaic pack GPL3 for
// algorithm 10 (AMAzE). If either of these packs are not included, it
// will silently use option 3 - AHD.
std::string demosaic = config.get_string_attribute("raw:Demosaic");
if (demosaic.size()) {
static const char* demosaic_algs[]
= { "linear",
"VNG",
"PPG",
"AHD",
"DCB",
"AHD-Mod",
"AFD",
"VCD",
"Mixed",
"LMMSE",
"AMaZE",
#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 16, 0)
"DHT",
"AAHD",
#endif
// Future demosaicing algorithms should go here
NULL };
size_t d;
for (d = 0; demosaic_algs[d]; d++)
if (Strutil::iequals(demosaic, demosaic_algs[d]))
break;
if (demosaic_algs[d])
m_processor->imgdata.params.user_qual = d;
else if (Strutil::iequals(demosaic, "none")) {
#ifdef LIBRAW_DECODER_FLATFIELD
// See if we can access the Bayer patterned data for this raw file
libraw_decoder_info_t decoder_info;
m_processor->get_decoder_info(&decoder_info);
if (!(decoder_info.decoder_flags & LIBRAW_DECODER_FLATFIELD)) {
errorf("Unable to extract unbayered data from file \"%s\"",
name);
return false;
}
#endif
// User has selected no demosaicing, so no processing needs to be done
m_process = false;
// This will read back a single, bayered channel
m_spec.nchannels = 1;
m_spec.channelnames.clear();
m_spec.channelnames.emplace_back("Y");
// Put the details about the filter pattern into the metadata
std::string filter(
libraw_filter_to_str(m_processor->imgdata.idata.filters));
if (filter.empty()) {
filter = "unknown";
}
m_spec.attribute("raw:FilterPattern", filter);
// Also, any previously set demosaicing options are void, so remove them
m_spec.erase_attribute("oiio:Colorspace");
m_spec.erase_attribute("raw:Colorspace");
m_spec.erase_attribute("raw:Exposure");
} else {
errorf("raw:Demosaic set to unknown value");
return false;
}
// Set the attribute in the output spec
m_spec.attribute("raw:Demosaic", demosaic);
} else {
m_processor->imgdata.params.user_qual = 3;
m_spec.attribute("raw:Demosaic", "AHD");
}
// Values returned by libraw are in linear, but are normalized based on the
// whitepoint / sensor / ISO and shooting conditions.
// Technically the transformation for each camera body / lens / setup
// must be solved bespoke, but we can get reasonable results by applying a 2.22222x scaler.
// This value can be obtained by:
// * Placing a neutral 18% reflective grey-card (Kodak R-27) at 45deg in midday sun (no clouds)
// * Spot measure the center of the card with a 1deg spot at f/8 in native ISO
// * Set camera to manual mode and set shutter, aperture and ISO as spot meter indicates
// * Take photo
// * Convert RAW file to linear exr via this library ensuring the following flags are set:
// * Overriding scale factor to 1.0x (raw:camera_to_scene_linear_scale 1.0)
// * Set output gamut to XYZ (raw:Colorspace XYZ)
// * Load image into scene linear editor (Nuke, Natron, etc)
// * Convert gamut from XYZ to Rec709 using ColorMatrix with Bradford scaling:
// [[ 3.1466669502 -1.6664582265 -0.4801943177 ]
// [-0.9955212125 1.9557543133 0.0397657062 ]
// [ 0.0635932301 -0.2145607754 1.1509330170 ]]
// * Desaturate image with Rec709 luma coefficients
// * Multiply image until grey chart measures 0.18
// * Re-run RAW conversion with this new multiplier (eg raw:camera_to_scene_linear_scale 2.2222)
// The default value of (1.0f / 0.45f) was solved in this way from a Canon 7D
if (config.find_attribute("raw:camera_to_scene_linear_scale") ||
// Add a simple on/off to apply the default scaling
config.find_attribute("raw:apply_scene_linear_scale")) {
m_camera_to_scene_linear_scale
= config.get_float_attribute("raw:camera_to_scene_linear_scale",
(1.0f / 0.45f));
m_do_scene_linear_scale = true;
// Store scene linear values in HALF datatype rather than UINT16
m_spec.set_format(TypeDesc::HALF);
m_spec.attribute("raw:camera_to_scene_linear_scale",
m_camera_to_scene_linear_scale);
}
// Highlight adjustment
// 0 = Clip
// 1 = Unclip
// 2 = Blend
// 3+ = Recovery
int highlight_mode = config.get_int_attribute("raw:HighlightMode", 0);
if (highlight_mode < 0 || highlight_mode > 9) {
errorf("raw:HighlightMode invalid value. range 0-9");
return false;
}
m_processor->imgdata.params.highlight = highlight_mode;
m_spec.attribute("raw:HighlightMode", highlight_mode);
// When the highlights are clipped, it can cause images to take on an apparent hue
// shift if all 3 channels aren't clipping uniformly. This often confuses HDRI merging
// applications, causing strange values in areas of high brightness (suns, speculars, etc).
// The balance_clamped option checks to see what the highest accepted value should be
// and then hard clamps all channels to this value.
// Enabling "balance_clamped" will change the return buffer type to HALF
int balance_clamped = config.get_int_attribute("raw:balance_clamped",
0); // default OFF
if (highlight_mode != 0 /*Clip*/) {
// FIXME: promote this debug message to a runtme warning
OIIO::debugf(
"%s",
"raw:balance_clamped will have no effect as raw:HighlightMode is not 0\n");
}
if (m_process && balance_clamped != 0 && highlight_mode == 0 /*Clip*/) {
m_spec.set_format(TypeDesc::HALF);
m_spec.attribute("raw:balance_clamped", balance_clamped);
// The following code can only run once the libraw processor is unpacked.
// As these values only have effect on the debayered images, it is ok
// to leave them unset the first time.
if (m_unpacked) {
float old_max_thr = m_processor->imgdata.params.adjust_maximum_thr;
// Disable max threshold for highlight adjustment
m_processor->imgdata.params.adjust_maximum_thr = 0.0f;
// Get unadjusted max value (need to force a read first)
ret = m_processor->raw2image_ex(/*subtract_black=*/true);
if (ret != LIBRAW_SUCCESS) {
errorf("HighlightMode adjustment detection read failed");
errorf("%s", libraw_strerror(ret));
return false;
}
if (m_processor->adjust_maximum() != LIBRAW_SUCCESS) {
errorf("HighlightMode minimum adjustment failed");
errorf("%s", libraw_strerror(ret));
return false;
}
float unadjusted = m_processor->imgdata.color.maximum;
// Set the max threshold to either the default 1.0, or user requested max
m_processor->imgdata.params.adjust_maximum_thr
= (old_max_thr == 0.0f) ? 1.0 : old_max_thr;
// Get new max value
if (m_processor->adjust_maximum() != LIBRAW_SUCCESS) {
errorf("HighlightMode maximum adjustment failed");
errorf("%s", libraw_strerror(ret));
return false;
}
float adjusted = m_processor->imgdata.color.maximum;
// Restore old max threshold
m_processor->imgdata.params.adjust_maximum_thr = old_max_thr;
if (unadjusted <= 0.0f) {
// invalid data
} else {
m_do_balance_clamped = true;
m_balanced_max = adjusted / unadjusted;
}
}
}
// Metadata
const libraw_image_sizes_t& sizes(m_processor->imgdata.sizes);
m_spec.attribute("PixelAspectRatio", (float)sizes.pixel_aspect);
// Libraw rotate the pixels automatically.
// The "flip" field gives the information about this rotation.
// This rotation is dependent on the camera orientation sensor.
// This information may be important for the user.
if (sizes.flip != 0) {
m_spec.attribute("raw:flip", sizes.flip);
}
// FIXME: sizes. top_margin, left_margin, raw_pitch, mask?
const libraw_iparams_t& idata(m_processor->imgdata.idata);
const libraw_colordata_t& color(m_processor->imgdata.color);
if (idata.make[0]) {
m_make = std::string(idata.make);
m_spec.attribute("Make", idata.make);
} else {
m_make.clear();
}
if (idata.model[0])
m_spec.attribute("Model", idata.model);
#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 17, 0)
if (idata.software[0])
m_spec.attribute("Software", idata.software);
else
#endif
if (color.model2[0])
m_spec.attribute("Software", color.model2);
// FIXME: idata. dng_version, is_foveon, colors, filters, cdesc
m_spec.attribute("Exif:Flash", (int)color.flash_used);
// FIXME -- all sorts of things in this struct
const libraw_imgother_t& other(m_processor->imgdata.other);
m_spec.attribute("Exif:ISOSpeedRatings", (int)other.iso_speed);
m_spec.attribute("ExposureTime", other.shutter);
m_spec.attribute("Exif:ShutterSpeedValue", -std::log2(other.shutter));
m_spec.attribute("FNumber", other.aperture);
m_spec.attribute("Exif:ApertureValue", 2.0f * std::log2(other.aperture));
m_spec.attribute("Exif:FocalLength", other.focal_len);
struct tm m_tm;
Sysutil::get_local_time(&m_processor->imgdata.other.timestamp, &m_tm);
char datetime[20];
strftime(datetime, 20, "%Y-%m-%d %H:%M:%S", &m_tm);
m_spec.attribute("DateTime", datetime);
add("raw", "ShotOrder", other.shot_order, false);
// FIXME: other.shot_order
if (other.desc[0])
m_spec.attribute("ImageDescription", other.desc);
if (other.artist[0])
m_spec.attribute("Artist", other.artist);
#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 17, 0)
if (other.parsed_gps.gpsparsed) {
add("GPS", "Latitude", other.parsed_gps.latitude, false, 0.0f);
# if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 20, 0)
add("GPS", "Longitude", other.parsed_gps.longitude, false, 0.0f);
# else
add("GPS", "Longitude", other.parsed_gps.longtitude, false,
0.0f); // N.B. wrong spelling!
# endif
add("GPS", "TimeStamp", other.parsed_gps.gpstimestamp, false, 0.0f);
add("GPS", "Altitude", other.parsed_gps.altitude, false, 0.0f);
add("GPS", "LatitudeRef", string_view(&other.parsed_gps.latref, 1),
false);
add("GPS", "LongitudeRef", string_view(&other.parsed_gps.longref, 1),
false);
add("GPS", "AltitudeRef", string_view(&other.parsed_gps.altref, 1),
false);
add("GPS", "Status", string_view(&other.parsed_gps.gpsstatus, 1),
false);
}
#endif
#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 20, 0)
const libraw_makernotes_t& makernotes(m_processor->imgdata.makernotes);
const libraw_metadata_common_t& common(makernotes.common);
// float FlashEC;
// float FlashGN;
// float CameraTemperature;
// float SensorTemperature;
// float SensorTemperature2;
// float LensTemperature;
// float AmbientTemperature;
// float BatteryTemperature;
// float exifAmbientTemperature;
add("Exif", "Humidity", common.exifHumidity, false, 0.0f);
add("Exif", "Pressure", common.exifPressure, false, 0.0f);
add("Exif", "WaterDepth", common.exifWaterDepth, false, 0.0f);
add("Exif", "Acceleration", common.exifAcceleration, false, 0.0f);
add("Exif", "CameraElevactionAngle", common.exifCameraElevationAngle, false,
0.0f);
// float real_ISO;
#elif LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 19, 0)
// float FlashEC;
// float FlashGN;
// float CameraTemperature;
// float SensorTemperature;
// float SensorTemperature2;
// float LensTemperature;
// float AmbientTemperature;
// float BatteryTemperature;
// float exifAmbientTemperature;
add("Exif", "Humidity", other.exifHumidity, false, 0.0f);
add("Exif", "Pressure", other.exifPressure, false, 0.0f);
add("Exif", "WaterDepth", other.exifWaterDepth, false, 0.0f);
add("Exif", "Acceleration", other.exifAcceleration, false, 0.0f);
add("Exif", "CameraElevactionAngle", other.exifCameraElevationAngle, false,
0.0f);
// float real_ISO;
#endif
// libraw reoriented the image for us, so squash any orientation
// metadata we may have found in the Exif. Preserve the original as
// "raw:Orientation".
int ori = m_spec.get_int_attribute("Orientation", 1);
if (ori > 1)
m_spec.attribute("raw:Orientation", ori);
m_spec.attribute("Orientation", 1);
// FIXME -- thumbnail possibly in m_processor->imgdata.thumbnail
get_lensinfo();
get_shootinginfo();
get_makernotes();
return true;
}
// Helper macro: add metadata with the same name as mn.name, but don't
// force it if it's the `ignore` value.
#define MAKER(name, ignore) add(m_make, #name, mn.name, false, ignore)
// Helper: add metadata, no matter what the value.
#define MAKERF(name) add(m_make, #name, mn.name, true)
void
RawInput::get_makernotes()
{
if (Strutil::istarts_with(m_make, "Canon"))
get_makernotes_canon();
else if (Strutil::istarts_with(m_make, "Nikon"))
get_makernotes_nikon();
else if (Strutil::istarts_with(m_make, "Olympus"))
get_makernotes_olympus();
else if (Strutil::istarts_with(m_make, "Fuji"))
get_makernotes_fuji();
else if (Strutil::istarts_with(m_make, "Kodak"))
get_makernotes_kodak();
else if (Strutil::istarts_with(m_make, "Panasonic"))
get_makernotes_panasonic();
else if (Strutil::istarts_with(m_make, "Pentax"))
get_makernotes_pentax();
else if (Strutil::istarts_with(m_make, "Sony"))
get_makernotes_sony();
}
void
RawInput::get_makernotes_canon()
{
#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 18, 0)
auto const& mn(m_processor->imgdata.makernotes.canon);
// MAKER (CanonColorDataVer, 0);
// MAKER (CanonColorDataSubVer, 0);
MAKERF(SpecularWhiteLevel);
MAKERF(ChannelBlackLevel);
MAKERF(AverageBlackLevel);
MAKERF(MeteringMode);
MAKERF(SpotMeteringMode);
MAKERF(FlashMeteringMode);
MAKERF(FlashExposureLock);
MAKERF(ExposureMode);
MAKERF(AESetting);
MAKERF(ImageStabilization);
# if LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0)
MAKERF(HighlightTonePriority);
MAKERF(FocusMode);
MAKER(AFPoint, 0);
MAKERF(FocusContinuous);
// short AFPointsInFocus30D;
// uchar AFPointsInFocus1D[8];
// ushort AFPointsInFocus5D; /* bytes in reverse*/
MAKERF(AFAreaMode);
if (mn.AFAreaMode) {
MAKERF(NumAFPoints);
MAKERF(ValidAFPoints);
MAKERF(AFImageWidth);
MAKERF(AFImageHeight);