Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exiv2 does not find certain AF tags for the Nikon D850 #646

Closed
mallman opened this issue Jan 9, 2019 · 25 comments
Closed

Exiv2 does not find certain AF tags for the Nikon D850 #646

mallman opened this issue Jan 9, 2019 · 25 comments
Assignees
Milestone

Comments

@mallman
Copy link
Collaborator

mallman commented Jan 9, 2019

I'm looking for the location and size metadata of the contrast-detect AF point in my Nikon D850 raw files. Such tags include Exif.NikonAf2.AFAreaXPosition, Exif.NikonAf2.AFAreaYPosition, Exif.NikonAf2.AFAreaWidth, etc. Exiv2 finds the correct values for the D810 but not for the D850.

Exiftool does return this information for the D850, so I checked its source. Apparently the file offset of this metadata depends on the value of Exif.NikonAf2.Version. For example, compare

https://github.com/exiftool/exiftool/blob/9e0ce916748abb56dbb8593d2184445080f678d0/lib/Image/ExifTool/Nikon.pm#L3519-L3525

with

https://github.com/exiftool/exiftool/blob/9e0ce916748abb56dbb8593d2184445080f678d0/lib/Image/ExifTool/Nikon.pm#L3627-L3633.

Note specifically the different Condition keys.

@clanmills
Copy link
Collaborator

Can you provide a sample image with the D850. I have 7xD810 images, however if you have additional images, they will be useful.

@clanmills
Copy link
Collaborator

@clanmills
Copy link
Collaborator

OK. A little progress. I downloaded DSC_6983.NEF from the sample gallery.

764 rmills@rmillsmbp:~/gnu/github/exiv2/exiv2 $ exiv2 -pa --grep NikonAf2.Version --grep Make -g Model /mmHD/Users/rmills/Jenkins/testfiles/raw/nikon/RAW_NIKON_D810_LARGE_12BIT_LOSS.NEF
Exif.Image.Make                              Ascii      18  NIKON CORPORATION
Exif.Image.Model                             Ascii      11  NIKON D810
Exif.Photo.MakerNote                         Undefined 168926  (Binary value suppressed)
Exif.MakerNote.Offset                        Long        1  13448
Exif.MakerNote.ByteOrder                     Ascii       3  II
Exif.NikonAf2.Version                        Undefined   4  1.00
765 rmills@rmillsmbp:~/gnu/github/exiv2/exiv2 $ exiv2 -pa --grep NikonAf2.Version --grep Make -g Model ~/Downloads/DSC_6983.NEF 
Exif.Image.Make                              Ascii      18  NIKON CORPORATION
Exif.Image.Model                             Ascii      11  NIKON D850
Exif.Photo.MakerNote                         Undefined 198014  (Binary value suppressed)
Exif.MakerNote.Offset                        Long        1  13500
Exif.MakerNote.ByteOrder                     Ascii       3  II
Exif.NikonAf2.Version                        Undefined   4  1.01
Xmp.tiff.Make                                XmpText    17  NIKON CORPORATION
Xmp.tiff.Model                               XmpText    10  NIKON D850
766 rmills@rmillsmbp:~/gnu/github/exiv2/exiv2 $ 

I didn't write Exiv2, and don't understand how the makernote decoder performs its magic. However, that makes the puzzle more interesting.

I'll update you when I've made some progress. Here are some thoughts for the moment.

I'm fairly sure that the AF2 structure is tag 0xb7

774 rmills@rmillsmbp:~/gnu/github/exiv2/exiv2 $ finder "*.cpp" $G -Hn -i af2 $___
./src/tags_int.cpp:109:        { nikonAf2Id,      "Makernote", "NikonAf2",     Nikon3MakerNote::tagListAf2    },
./src/tiffimage_int.cpp:224:    extern const ArrayCfg nikonAf2Cfg = {
./src/tiffimage_int.cpp:225:        nikonAf2Id,       // Group for the elements
./src/tiffimage_int.cpp:235:    extern const ArrayDef nikonAf2Def[] = {
./src/tiffimage_int.cpp:934:        { Tag::root, nikonAf2Id,       nikon3Id,         0x00b7    },
./src/tiffimage_int.cpp:1276:        {    0x00b7, nikon3Id,         EXV_BINARY_ARRAY(nikonAf2Cfg, nikonAf2Def)},  <-- THIS
./src/tiffimage_int.cpp:1303:        {  Tag::all, nikonAf2Id,       newTiffBinaryElement                      },

And this is what's in it:

./src/nikonmn_int.cpp:888:    const TagInfo Nikon3MakerNote::tagInfoAf2_[] = {
./src/nikonmn_int.cpp:889:        TagInfo(  0, "Version", N_("Version"), N_("Version"), nikonAf2Id, makerTags, undefined, 4, printExifVersion),
./src/nikonmn_int.cpp:890:        TagInfo(  4, "ContrastDetectAF", N_("Contrast Detect AF"), N_("Contrast detect AF"), nikonAf2Id, makerTags, unsignedByte, 1, EXV_PRINT_TAG(nikonOffOn)),
./src/nikonmn_int.cpp:891:        TagInfo(  5, "AFAreaMode", N_("AF Area Mode"), N_("AF area mode"), nikonAf2Id, makerTags, unsignedByte, 1, printValue),
./src/nikonmn_int.cpp:892:        TagInfo(  6, "PhaseDetectAF", N_("Phase Detect AF"), N_("Phase detect AF"), nikonAf2Id, makerTags, unsignedByte, 1, EXV_PRINT_TAG(nikonPhaseDetectAF)),
./src/nikonmn_int.cpp:893:        TagInfo(  7, "PrimaryAFPoint", N_("Primary AF Point"), N_("Primary AF point"), nikonAf2Id, makerTags, unsignedByte, 1, printValue),
./src/nikonmn_int.cpp:894:        TagInfo(  8, "AFPointsUsed", N_("AF Points Used"), N_("AF points used"), nikonAf2Id, makerTags, unsignedByte, 7, printValue),
./src/nikonmn_int.cpp:895:        TagInfo( 16, "AFImageWidth", N_("AF Image Width"), N_("AF image width"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
./src/nikonmn_int.cpp:896:        TagInfo( 18, "AFImageHeight", N_("AF Image Height"), N_("AF image height"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
./src/nikonmn_int.cpp:897:        TagInfo( 20, "AFAreaXPosition", N_("AF Area X Position"), N_("AF area x position"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
./src/nikonmn_int.cpp:898:        TagInfo( 22, "AFAreaYPosition", N_("AF Area Y Position"), N_("AF area y position"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
./src/nikonmn_int.cpp:899:        TagInfo( 24, "AFAreaWidth", N_("AF Area Width"), N_("AF area width"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
./src/nikonmn_int.cpp:900:        TagInfo( 26, "AFAreaHeight", N_("AF Area Height"), N_("AF area height"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
./src/nikonmn_int.cpp:901:        TagInfo( 28, "ContrastDetectAFInFocus", N_("Contrast Detect AF In Focus"), N_("Contrast detect AF in focus"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
./src/nikonmn_int.cpp:903:        TagInfo(0xffff, "(UnknownNikonAf2Tag)", "(UnknownNikonAf2Tag)", N_("Unknown Nikon Auto Focus 2 Tag"), nikonAf2Id, makerTags, unsignedByte, 1, printValue)
./src/nikonmn_int.cpp:906:    const TagInfo* Nikon3MakerNote::tagListAf2()
./src/nikonmn_int.cpp:908:        return tagInfoAf2_;
./src/easyaccess.cpp:526:            "Exif.NikonAf2.AFPointsUsed",
./src/easyaccess.cpp:527:            "Exif.NikonAf2.PrimaryAFPoint",
./src/minoltamn_int.cpp:1924:        { 45741, "Minolta AF200mm F2.8G x2 | "
775 rmills@rmillsmbp:~/gnu/github/exiv2/exiv2 $ 

When I dump the file with the recursive parser (which I wrote):

STRUCTURE OF TIFF FILE (II): DSC_6983.NEF
 address |    tag                              |      type |    count |    offset | value
42634234 | 0x00fe NewSubfileType               |      LONG |        1 |           | 1
...
42634462 | 0x014a SubIFDs                      |      LONG |        3 |       480 | 269132 269252 269480
  STRUCTURE OF TIFF FILE (II): DSC_6983.NEF
   address |    tag                              |      type |    count |    offset | value
    269134 | 0x00fe NewSubfileType               |      LONG |        1 |           | 1
...
    269218 | 0x0213 YCbCrPositioning             |     SHORT |        1 |           | 2
  END DSC_6983.NEF
  STRUCTURE OF TIFF FILE (II): DSC_6983.NEF
   address |    tag                              |      type |    count |    offset | value
    269254 | 0x00fe NewSubfileType               |      LONG |        1 |           | 0
...
    269446 | 0x9217 SensingMethod                |     SHORT |        1 |           | 2
  END DSC_6983.NEF
  STRUCTURE OF TIFF FILE (II): DSC_6983.NEF
   address |    tag                              |      type |    count |    offset | value
    269482 | 0x00fe NewSubfileType               |      LONG |        1 |           | 1
...
    269566 | 0x0213 YCbCrPositioning             |     SHORT |        1 |           | 2
  END DSC_6983.NEF
42634474 | 0x0214 ReferenceBlackWhite          |  RATIONAL |        6 |       492 | 0/1 255/1 0/1 255/1 0/1 ...
42634486 | 0x02bc XMLPacket                    |      BYTE |    12288 |  42634562 | <?xpacket begin="..." id="W5M0Mp ...
42634498 | 0x8298 Copyright                    |     ASCII |       55 |     12828 | ............................... ...
42634510 | 0x8769 ExifTag                      |      LONG |        1 |           | 12904
  STRUCTURE OF TIFF FILE (II): DSC_6983.NEF
   address |    tag                              |      type |    count |    offset | value
     12906 | 0x829a ExposureTime                 |  RATIONAL |        1 |     13344 | 10/2000
...
     13098 | 0x920a FocalLength                  |  RATIONAL |        1 |     13440 | 1050/10
     13110 | 0x927c MakerNote                    | UNDEFINED |   198014 |     13500 | Nikon.....II*.....L.........0211 ...
    STRUCTURE OF TIFF FILE (II): MemIo
     address |    tag                              |      type |    count |    offset | value
          10 | 0x0001 Version                      | UNDEFINED |        4 |           | 0211
          22 | 0x0004 Quality                      |     ASCII |        8 |       926 | RAW    
          34 | 0x0005 WhiteBalance                 |     ASCII |       13 |       934 | SUNNY       
          46 | 0x0007 Focus                        |     ASCII |        7 |       950 | AF-S  
...
 -->     838 | 0x00b7                              | UNDEFINED |       84 |     27022 | 0101........................... ...
...
    END MemIo
     13122 | 0x9286 UserComment                  | UNDEFINED |       44 |     13448 | ASCII.......................... ...
     13134 | 0x9290 SubSecTime                   |     ASCII |        3 |           | 92
     13146 | 0x9291 SubSecTimeOriginal           |     ASCII |        3 |           | 92
...
  END DSC_6983.NEF
42634522 | 0x8825 GPSTag                       |      LONG |        1 |           | 211514
42634534 | 0x9003 DateTimeOriginal             |     ASCII |       20 |     12884 | 2017:10:10 12:02:59
42634546 | 0x9216 TIFFEPStandardID             |      BYTE |        4 |           | ...
END DSC_6983.NEF

So, I can see 84 bytes of AF2 data in the file. Story to be continued.

@clanmills
Copy link
Collaborator

I see that Exiftool is very interested in the same 84 bytes:

$ exiftool -v5 ~/Downloads/DSC_6983.NEF
...
  | | | 69) AFInfo2 (SubDirectory) -->
  | | |     - Tag 0x00b7 (84 bytes, undef[84]):
  | | |         9e54: 30 31 30 31 00 00 07 00 00 00 00 00 00 00 00 00 [0101............]
  | | |         9e64: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
  | | |         9e74: 00 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
  | | |         9e84: 00 00 00 00 00 80 00 00 00 00 00 00 00 00 00 00 [................]
  | | |         9e94: 00 00 00 00 30 00 00 00 00 00 00 00 00 00 00 00 [....0...........]
  | | |         9ea4: 00 00 00 00                                     [....]
  | | | + [BinaryData directory, 84 bytes]
  | | | | AFInfo2Version = 0101
  | | | | - Tag 0x0000 (4 bytes, undef[4]):
  | | | |     9e54: 30 31 30 31                                     [0101]
  | | | | ContrastDetectAF = 0
  | | | | - Tag 0x0004 (1 bytes, int8u[1]):
  | | | |     9e58: 00                                              [.]
  | | | | AFAreaMode = 0
  | | | | - Tag 0x0005 (1 bytes, int8u[1]):
  | | | |     9e59: 00                                              [.]
  | | | | PhaseDetectAF = 7
  | | | | - Tag 0x0006 (1 bytes, int8u[1]):
  | | | |     9e5a: 07                                              [.]
  | | | | AFPointsUsed = 
  | | | | - Tag 0x0008 (20 bytes, undef[20]):
  | | | |     9e5c: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
  | | | |     9e6c: 00 00 00 00                                     [....]
  | | | | PrimaryAFPoint = 48
  | | | | - Tag 0x0044 (1 bytes, int8u[1]):
  | | | |     9e98: 30                                              [0]
... 

And I can extract those bytes bytes with dd and dump them with dmpf (my homemade od on steroids).

13500 = Offset to the MakerNote
27022 = Offset within the MakerNote to data in tag 0x00b7
10 = Don't know yet

778 rmills@rmillsmbp:~/gnu/github/exiv2/exiv2 $ dd bs=1 skip=$((13500+27022+10)) count=84 if=~/Downloads/DSC_6983.NEF 2>/dev/null | dmpf -
       0        0: 0101............  ->  30 31 30 31 00 00 07 00 00 00 00 00 00 00 00 00
    0x10       16: ................  ->  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x20       32: ................  ->  00 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x30       48: ................  ->  00 00 00 00 00 80 00 00 00 00 00 00 00 00 00 00
    0x40       64: ....0...........  ->  00 00 00 00 30 00 00 00 00 00 00 00 00 00 00 00
    0x50       80: ....              ->  00 00 00 00
779 rmills@rmillsmbp:~/gnu/github/exiv2/exiv2 $ 

And there the trail dies. There's nothing interesting in those 84 bytes. And exiv2 is reporting the same very uninteresting data.

780 rmills@rmillsmbp:~/gnu/github/exiv2/exiv2 $ exiv2 -pa --grep af2/i ~/Downloads/DSC_6983.NEF 
Exif.NikonAf2.Version                        Undefined   4  1.01
Exif.NikonAf2.ContrastDetectAF               Byte        1  Off
Exif.NikonAf2.AFAreaMode                     Byte        1  0
Exif.NikonAf2.PhaseDetectAF                  Byte        1  (7)
Exif.NikonAf2.PrimaryAFPoint                 Byte        1  0
Exif.NikonAf2.AFPointsUsed                   Byte        7  0 0 0 0 0 0 0
Exif.NikonAf2.AFImageWidth                   Short       1  0
Exif.NikonAf2.AFImageHeight                  Short       1  0
Exif.NikonAf2.AFAreaXPosition                Short       1  0
Exif.NikonAf2.AFAreaYPosition                Short       1  0
Exif.NikonAf2.AFAreaWidth                    Short       1  0
Exif.NikonAf2.AFAreaHeight                   Short       1  0
Exif.NikonAf2.ContrastDetectAFInFocus        Short       1  0
781 rmills@rmillsmbp:~/gnu/github/exiv2/exiv2 $

So. I'm back to square one. Can you provide a sample image. Can you extract your items of interest with exiftool and share that output with me, please?

@mallman
Copy link
Collaborator Author

mallman commented Jan 10, 2019

Hi Robin,

It appears the RAW file you downloaded was not captured with "contrast detect" AF, and I believe that's the reason you're coming up empty. I have a file I'll share with you, _DSC8437.NEF, which was shot with contrast detect AF.

[msa@ra Desktop]$ exiftool DSC_6983.NEF | grep "Contrast Detect AF"
Contrast Detect AF              : Off
[msa@ra Desktop]$ exiftool DSC_6983.NEF | grep "AF Area X Position"
[msa@ra Desktop]$ exiftool _DSC8437.NEF | grep "Contrast Detect AF"
Contrast Detect AF              : On
Contrast Detect AF In Focus     : Yes
[msa@ra Desktop]$ exiftool _DSC8437.NEF | grep "AF Area X Position"
AF Area X Position              : 3950
[msa@ra Desktop]$ 

I've uploaded the file to my Dropbox, and I'll send the link to the e-mail address you've linked to your Github profile.

Thanks so much for your help with this.

Michael

@clanmills
Copy link
Collaborator

Michael

Thanks for sending your file. Here's what I see:

1074 rmills@rmillsmbp:~/Downloads $ exiv2 -pR _DSC8437.NEF | grep -e 00b7 -e MakerNote
     13110 | 0x927c MakerNote                    | UNDEFINED |   203064 |     13500 | Nikon.....II*.....L.........0211 ...
         838 | 0x00b7                              | UNDEFINED |       84 |     26286 | 0101........................... ...

The MakerNote is 203064 bytes at offset 13500. Tag 0x00b7 is 84 bytes at offset 26286 in the maker note. Once more, I have to add 10 to get the location of the data.

The 10 is correct, my old brain can't explain it, yet!. It's clear that the -pR option locates and dumps the tag correctly as it starts with the ascii bytes "0101"

1075 rmills@rmillsmbp:~/Downloads $ dd bs=1 skip=$((13500+26286+10)) count=84 if=~/Downloads/_DSC8437.NEF 2>/dev/null | dmpf -
       0        0: 0101............  ->  30 31 30 31 01 01 00 00 00 00 00 00 00 00 00 00
    0x10       16: ................  ->  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x20       32: ................  ->  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x30       48: ................  ->  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    0x40       64: ......@ ..n.7...  ->  00 00 00 00 00 00 40 20 80 15 6e 0f 37 0b b3 01
    0x50       80: h...              ->  68 01 01 00
1076 rmills@rmillsmbp:~/Downloads $ 

As you can see. 84 bytes. However it's not the land of zeros we saw in the other file. This is the same binary block that exiftool reveals with the -v5 option:

...
  | | | 69) AFInfo2 (SubDirectory) -->
  | | |     - Tag 0x00b7 (84 bytes, undef[84]):
  | | |         9b74: 30 31 30 31 01 01 00 00 00 00 00 00 00 00 00 00 [0101............]
  | | |         9b84: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
  | | |         9b94: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
  | | |         9ba4: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
  | | |         9bb4: 00 00 00 00 00 00 40 20 80 15 6e 0f 37 0b b3 01 [......@ ..n.7...]
  | | |         9bc4: 68 01 01 00                                     [h...]
...

Decoded as:

...
AF Image Width                  : 8256
AF Image Height                 : 5504
AF Area X Position              : 3950
AF Area Y Position              : 2871
AF Area Width                   : 435
AF Area Height                  : 360
Contrast Detect AF In Focus     : Yes
...
1084 rmills@rmillsmbp:~/Downloads $ 

I have a little collection of home-made utilities (such as dmpf), and one is hex.cpp:

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
	if ( argc > 1 ) {
		for ( int i = 1 ; i < argc ; i++ ) {
			char* arg = argv[i] ;
			printf("%s = %#x\n",arg,atoi(arg)) ;
		}
	}
	return 0 ;
}

And using this:

1090 rmills@rmillsmbp:~/Downloads $ hex 8256 5504 3950 2871 435 360
0x4020 0x8015 0x6e0f 0x370b 0x1b3 0x168
1091 rmills@rmillsmbp:~/Downloads $ 

And that's what's in the file (from bytes 70-80) in tag 0x007b

1096 rmills@rmillsmbp:~/Downloads $ dd bs=1 skip=$((13500+26286+10+70)) count=$((84-70-4)) if=~/Downloads/_DSC8437.NEF 2>/dev/null | dmpf -
       0        0: @ ..n.7...h...    ->  40 20 80 15 6e 0f 37 0b b3 01 68
1097 rmills@rmillsmbp:~/Downloads $ 

So we've identified the location of the data in the file. And now I have to figure out how to correctly decode the key/values. Here's what we're reporting:

1098 rmills@rmillsmbp:~/Downloads $ exiv2 -pa --grep AF2/i _DSC8437.NEF 
Exif.NikonAf2.Version                        Undefined   4  1.01
Exif.NikonAf2.ContrastDetectAF               Byte        1  On
Exif.NikonAf2.AFAreaMode                     Byte        1  1
Exif.NikonAf2.PhaseDetectAF                  Byte        1  Off
Exif.NikonAf2.PrimaryAFPoint                 Byte        1  0
Exif.NikonAf2.AFPointsUsed                   Byte        7  0 0 0 0 0 0 0
Exif.NikonAf2.AFImageWidth                   Short       1  0
Exif.NikonAf2.AFImageHeight                  Short       1  0
Exif.NikonAf2.AFAreaXPosition                Short       1  0
Exif.NikonAf2.AFAreaYPosition                Short       1  0
Exif.NikonAf2.AFAreaWidth                    Short       1  0
Exif.NikonAf2.AFAreaHeight                   Short       1  0
Exif.NikonAf2.ContrastDetectAFInFocus        Short       1  0
1099 rmills@rmillsmbp:~/Downloads $

You've noticed that "ContrastDetectAF" is set in your file, however we arge reporting lots of zeros.

I didn't write the metadata decoder and Andreas (the author) made such a good job that I've hardly ever visited that code. It uses a "visitor" model. The parser descends through the file and calls functions to decode different types of tags. I have to discover where tag 0x007b is encountered and "fix it" to respect Exif.NikonAf2.ContrastDetectAF.

Difficult? Unlikely. However with all puzzles, it'll take a little time. We're making progress.

@clanmills clanmills added this to the v0.27.1 milestone Jan 13, 2019
@mallman
Copy link
Collaborator Author

mallman commented Jan 13, 2019

Glad to see my file was helpful, and you're making such excellent progress!

@clanmills
Copy link
Collaborator

@mallman I'm pleased to be working on this. I've been working on Exiv2 since 2008 and have wondered for some time: How does class TiffReader actually work? I'm close to the "Eureka" moment.

@clanmills
Copy link
Collaborator

Almost there. The following table in src/nikonmn.cpp defines the layout of the binary metadata:

    // Nikon3 Auto Focus Tag Info
    const TagInfo Nikon3MakerNote::tagInfoAf2_[] = {
        TagInfo(  0, "Version", N_("Version"), N_("Version"), nikonAf2Id, makerTags, undefined, 4, printExifVersion),
        TagInfo(  4, "ContrastDetectAF", N_("Contrast Detect AF"), N_("Contrast detect AF"), nikonAf2Id, makerTags, unsignedByte, 1, EXV_PRINT_TAG(nikonOffOn)),
        TagInfo(  5, "AFAreaMode", N_("AF Area Mode"), N_("AF area mode"), nikonAf2Id, makerTags, unsignedByte, 1, printValue),
        TagInfo(  6, "PhaseDetectAF", N_("Phase Detect AF"), N_("Phase detect AF"), nikonAf2Id, makerTags, unsignedByte, 1, EXV_PRINT_TAG(nikonPhaseDetectAF)),
        TagInfo(  7, "PrimaryAFPoint", N_("Primary AF Point"), N_("Primary AF point"), nikonAf2Id, makerTags, unsignedByte, 1, printValue),
        TagInfo(  8, "AFPointsUsed", N_("AF Points Used"), N_("AF points used"), nikonAf2Id, makerTags, unsignedByte, 7, printValue),
        TagInfo( 16, "AFImageWidth", N_("AF Image Width"), N_("AF image width"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
        TagInfo( 18, "AFImageHeight", N_("AF Image Height"), N_("AF image height"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
        TagInfo( 20, "AFAreaXPosition", N_("AF Area X Position"), N_("AF area x position"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
        TagInfo( 22, "AFAreaYPosition", N_("AF Area Y Position"), N_("AF area y position"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
        TagInfo( 24, "AFAreaWidth", N_("AF Area Width"), N_("AF area width"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
        TagInfo( 26, "AFAreaHeight", N_("AF Area Height"), N_("AF area height"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
        TagInfo( 28, "ContrastDetectAFInFocus", N_("Contrast Detect AF In Focus"), N_("Contrast detect AF in focus"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
        // End of list marker
        TagInfo(0xffff, "(UnknownNikonAf2Tag)", "(UnknownNikonAf2Tag)", N_("Unknown Nikon Auto Focus 2 Tag"), nikonAf2Id, makerTags, unsignedByte, 1, printValue)
    };

Bytes 0-3, 4 and 5 are Version, ContrastDetectAF and AFAreaMode which are 1010, 1, 1 in Michael's file (and 1010,0,0 in the sample). When ContrastDetectAF and AFAreaMode are 1, (and the length is 84 bytes or more), metadata such as AFAreaWidth is not at byte 24, it's at 70 (or something like that).

When TiffReader::visitBinaryArray() reads 0x007b, it calls TiffReader::readTiffEntry() which creates a binary object for the 84 bytes. visitBinaryArray pushes this object for later processing in postList_.push_back(object)

Later on, TiffReader::postProcess() is called to decode the binary objects on postList_

Now I need to understand:

  1. how postProcess() reunites the 84 bytes with the table in Nikon3MakerNote::tagInfoAf2_.
  2. to respect ContrastDetectAF and AFAreaMode to decode the shorts at byte 70

This is fun. I'll nail this one in the next few days.

@clanmills
Copy link
Collaborator

clanmills commented Jan 14, 2019

postProcess(), sends the visitor to inspect every deferred object, and the metadata is created here:

    void TiffDecoder::decodeTiffEntry(const TiffEntryBase* object)
    {
        assert(object != 0);

        // Don't decode the entry if value is not set
        if (!object->pValue()) return;

        const DecoderFct decoderFct = findDecoderFct_(make_,
                                                      object->tag(),
                                                      object->group());
        // skip decoding if decoderFct == 0
        if (decoderFct) {
            EXV_CALL_MEMBER_FN(*this, decoderFct)(object);
        }
    } // TiffDecoder::decodeTiffEntry

I can get this to stop in the debugger at:

object->tag() = 24 (AFAreaWidth)
object->group() = 47 (nikonAf2Id)
make_ = NIKON CORPORATION

constant nikonAf2Id == object->group() == 47 is defined in enum IfdId in src/tags_int.cpp

Phil has the 84 byte data structure documented here: https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html (Search "Nikon AFInfo2 Tags")

We could length the table in nikonmn_int.cpp (to include the shorts at bytes 70+), however we'd have two entries for some keys such as:

TagInfo( 24, "AFAreaWidth", N_("AF Area Width"), N_("AF area width"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
...
TagInfo( 70, "AFAreaWidth", N_("AF Area Width"), N_("AF area width"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),

We know which to select from the value of ContrastDetectAF and AFAreaMode.

If we can set decoderFct == 0, we can skip "early" shorts. This has to be achieved in a generic data-driven way to achieve the following effect:

if ( object->group() == nikonAf2Id && ContrastDetectAF == 1 && object->tag() == something && pure-magic ) {
  decoderFct = 0 ;
}

I'll work on something else for a little while to rest the brain, then run 10k to think about this.

@clanmills
Copy link
Collaborator

I had a two good ideas while running:

  1. Define two decoding structures in nikonmn_int.cpp
    a) The existing structure nikonAf2Id
    b) A new structure (very similar of course) nikonAf2IdA with AFArea at bytes 70+
    When we read the 84 bytes when can "sniff the data" to decide to use nikonAf2Id or nikonAf2IdA

  2. We should ensure that the binary decoding structures never reference data outside the block.
    The decoding structures have the index, data-type, count. For example 1 unsignedShort at offset 24.

TagInfo( 24, "AFAreaWidth", N_("AF Area Width"), N_("AF area width")
   , nikonAf2Id, makerTags, unsignedShort, 1, printValue),

It's obviously possible to run the array of TagInfo and calculate the maximum offset expected. We should ensure that we never overflow the data read (in this case 84 bytes).

I know the data is:

  1. Read from file at TiffReader::visitBinaryArray() when it reads Tag 0x007b. It calls TiffReader::readTiffEntry() to put the data into a binary 'object' which will be subsequently post-processed.

  2. Converted to Exiv2::Metadatum in created in TiffDecoder::decodeTiffEntry (code above)

There's a rather complex dance with the visitor code. It appears to create an Array of values (called elements_ in the code), and then he runs a loop on those values to call decodeTiffEntry().

The crux of this dance is nikonAf2Id (47). By the time we arrive in decodeTiffEntry(), this->group() is nikonAf2Id. However in the dance about, it's nikon3Id (40) which might be the maker note.

Tomorrow: More time in the debugger to understand this code. Or run another 10k tomorrow and think.

@clanmills
Copy link
Collaborator

More progress. Tag 0x007b is born as a nikonAf2ld and cannot be modified after birth! It's defined in this array in tiffimage_int.cpp

        // Nikon3 makernote
        { Tag::next, nikon3Id,         ignoreTiffComponent                       },
        {    0x0011, nikon3Id,         newTiffSubIfd<nikonPvId>                  },
        {    0x001f, nikon3Id,         EXV_BINARY_ARRAY(nikonVrCfg, nikonVrDef)  },
        {    0x0023, nikon3Id,         EXV_BINARY_ARRAY(nikonPcCfg, nikonPcDef)  },
        {    0x0024, nikon3Id,         EXV_BINARY_ARRAY(nikonWtCfg, nikonWtDef)  },
        {    0x0025, nikon3Id,         EXV_BINARY_ARRAY(nikonIiCfg, nikonIiDef)  },
        {    0x0088, nikon3Id,         EXV_BINARY_ARRAY(nikonAfCfg, nikonAfDef)  },
        {    0x0091, nikon3Id,         EXV_COMPLEX_BINARY_ARRAY(nikonSiSet, nikonSelector) },
        {    0x0097, nikon3Id,         EXV_COMPLEX_BINARY_ARRAY(nikonCbSet, nikonSelector) },
        {    0x0098, nikon3Id,         EXV_COMPLEX_BINARY_ARRAY(nikonLdSet, nikonSelector) },
        {    0x00a8, nikon3Id,         EXV_COMPLEX_BINARY_ARRAY(nikonFlSet, nikonSelector) },
        {    0x00b0, nikon3Id,         EXV_BINARY_ARRAY(nikonMeCfg, nikonMeDef)  },
        {    0x00b7, nikon3Id,         EXV_BINARY_ARRAY(nikonAf2Cfg, nikonAf2Def)},
        {    0x00b8, nikon3Id,         EXV_BINARY_ARRAY(nikonFiCfg, nikonFiDef)  },
        {    0x00b9, nikon3Id,         EXV_BINARY_ARRAY(nikonAFTCfg, nikonAFTDef)  },
        {  Tag::all, nikon3Id,         newTiffEntry                              },

We have to study the declaration EXV_COMPLEX_BINARY_ARRAY. I suspect that the function nikonSelector() is called to determine which of the possible ArrayCfgIds should be used. We'll have to write a new selector which will sniff the data and return nikonAf2Cfg or nikonAf2ACfg.

nikonSelector() is in src/makernote_int.cpp

    int nikonSelector(uint16_t tag, const byte* pData, uint32_t size, TiffComponent* const /*pRoot*/)
    {
        if (size < 4) return -1;
        const NikonArrayIdx* aix = find(nikonArrayIdx, NikonArrayIdx::Key(tag, reinterpret_cast<const char*>(pData), size));
        return aix == 0 ? -1 : aix->idx_;
    }

I've always suspected that Andreas' tiff parsing engine was ingenious. And now I know that it is ingenious.

@mallman
Copy link
Collaborator Author

mallman commented Jan 14, 2019

Very exciting work!

As an aside, I'll mention I'm migrating the way I extract image metadata for a Mac photo app I'm writing from the built-in ImageIO framework to Exiv2. Sadly, the amount of metadata we get through ImageIO is quite anemic. For example, I can extract the GPS time of day (Exif.GPSInfo.GPSTimeStamp) (which does not include the date), but not the accompanying date (Exif.GPSInfo.GPSDateStamp). How useful is that??? I've verified that the GPS date is clearly available in the files I'm reading by inspecting them with exifprint.

Another problem I've discovered is related to another discovery I've recently made.

In all the years I've used Aperture to store my photos (since v1.1)—when I've adjusted the timezone in a file I assumed Aperture was just setting a value in its metadata database. And indeed it does. However, it also alters the exif data in the raw file itself! Heresy! Indeed, it alters other fields as well! I thought Aperture never modified the raw files, and this is a very disappointing discovery for me. One of the concerns I have regarding any modification of raw files is inadvertent file corruption, either because of a bug in the modifying code or because a bit gets flipped when the file is rewritten.

The other problem is that—even though Aperture writes the original, unaltered date/time in another field (namely Exif.Image.DateTimeOriginal)—ImageIO does not read it! So I cannot get the original date/time from RAW files modified by Aperture using the ImageIO metadata parser. Fail!

Which all goes to say I really appreciate that we have an open-source alternative such as Exiv2.

Aside from basic metadata extraction, I want to overlay the contrast detect focus area over an image so I can see in post exactly where I set focus. This is where your work comes into play.

Cheers.

@clanmills
Copy link
Collaborator

Open Source is much better than Apple/Adobe/Google/Microsoft products because you have direct access to the author/maintainer. If you report anything to Apple about Aperture, it's unlikely that anything will happen as I believe it's no longer maintained. However, Apple Radar support is OK. I've reported a couple of issues concerning Xcode/C++, and been pleased by their response.

I maintain Exiv2 on a Mac and use Parallels to run VMs for Linux and Windows. About 2012, I wrote a Cocoa/Objective-C++ application to display metadata. Exiv2 was much much faster than Apple's code. About 100x faster, I think. I concluded that NSImage (or whatever I was used to read the file) built a frame-buffer with all the pixels. Exiv2 doesn't do that. Exiv2 reads the metadata and nothing else. A typical JPEG is about 4mb, of which about 10k is Metadata. Raw images, 40mb of which about 10k is Metadata.

$ exiv2 --verbose --force -ea ~/Downloads/_DSC8437.NEF 
File 1/1: /Users/rmills/Downloads/_DSC8437.NEF
Writing Exif data from /Users/rmills/Downloads/_DSC8437.NEF to /Users/rmills/Downloads/_DSC8437.exv
Writing XMP data from /Users/rmills/Downloads/_DSC8437.NEF to /Users/rmills/Downloads/_DSC8437.exv
Warning: Exif tag Exif.NikonPreview.JPEGInterchangeFormatLength not encoded
Warning: Exif IFD NikonPreview not encoded
Warning: Exif tag Exif.Photo.MakerNote not encoded
Warning: Exif tag Exif.NikonSi02xx.0x027a not encoded
Warning: Exif tag Exif.Nikon3.0x00bc not encoded
$ ls -l ~/Downloads/_DSC8437.*
-rw-r--r--@ 1 rmills  staff  62069251 10 Jan 20:19 /Users/rmills/Downloads/_DSC8437.NEF
-rw-r--r--  1 rmills  staff     11372 15 Jan 10:16 /Users/rmills/Downloads/_DSC8437.exv

If you're looking for a commercial alternative to Aperture for raw images, Exiv2 is used by AlienSkin Exposure. They gave me a license. Oh, and they paid for lunch when my wife and I went to visit them on vacation in Raleigh, NC last year. Exiv2 is also used by FotoStation who told me to use their 7 day trial license to fix something for them! I wonder if they'll buy me lunch when I visit? We're thinking about Norway for our summer vacation.

I hope to solve your puzzle today. For sure I can provide you with a bash script to extract the 0x007b tag using exiv2 -pR and dd. If I cannot solve this in class TiffReader today, we may have to wait for Exiv2 v0.28.

We are planning to rewrite the Tiff code to support BigTiff (64 bit tiff) for v0.28. Tiff is "the heart" of Exiv2 because Exif Metadata is stored as an embedded tiff. And most Raw formats are based on Tiff. Another member of the team has accepted this challenge. I'm highly motivated to work on your issue so that I can help Luis with BigTiff. We'd like to reuse Andreas' robust TiffVisitor code. To do that we have to understand it!

@clanmills
Copy link
Collaborator

Eureka:

Exif.NikonAf22.Version                       Undefined   4  1.01
Exif.NikonAf22.ContrastDetectAF              Byte        1  On
Exif.NikonAf22.AFAreaMode                    Byte        1  1
Exif.NikonAf22.PhaseDetectAF                 Byte        1  Off
Exif.NikonAf22.PrimaryAFPoint                Byte        1  0
Exif.NikonAf22.AFPointsUsed                  Byte        7  0 0 0 0 0 0 0
Exif.NikonAf22.AFImageWidth                  Short       1  8256
Exif.NikonAf22.AFImageHeight                 Short       1  5504
Exif.NikonAf22.AFAreaXPosition               Short       1  3950
Exif.NikonAf22.AFAreaYPosition               Short       1  2871
Exif.NikonAf22.AFAreaWidth                   Short       1  435
Exif.NikonAf22.AFAreaHeight                  Short       1  360
Exif.NikonAf22.ContrastDetectAFInFocus       Byte        2  1 0

There's something not quite right. You'll see the key changed its name from Exif.NikonAf2.AFImageWidth to Exif.NikonAf22.AFImageWidth. Maybe that's not fixable. Exif = Family Name, NikonAf2 = Group Name, AFImageWidth = Tag Name. Every group has a unique name. And I have two groups. One for the "old" definitions and one for the 84 byte tag with the data at bytes 70+. Something to debug tomorrow.

Very happy. I'm going to run 10k to celebrate. It'll be after sunset when I get back.

I should be able to get this into the code this week.

@mallman
Copy link
Collaborator Author

mallman commented Jan 15, 2019

Eureka:

👏

There's something not quite right. You'll see the key changed its name from Exif.NikonAf2.AFImageWidth to Exif.NikonAf22.AFImageWidth.

I can live with that! 😉

Very happy. I'm going to run 10k to celebrate.

Sounds appropriate! (And healthy!)

@mallman
Copy link
Collaborator Author

mallman commented Jan 15, 2019

I concluded that NSImage (or whatever I was used to read the file) built a frame-buffer with all the pixels. Exiv2 doesn't do that.

FWIW, the Apple API call I was using, namely CGImageSourceCopyPropertiesAtIndex, doesn't read the image data either. And it performs quite well. But then performance wasn't my problem.

I've switched over to Exiv2 with the help of an awesome ObjC wrapper I found on Github, https://github.com/sdrpa/Exiv2Framework. Even though it's 3 years old, it still works with the latest build of Exiv2. Using this framework, accessing Exiv2 from Swift is a breeze. The code looks like

import Exiv2

guard let metadata = FLTImageMetadata(imageAt: fileUrl) else { return }
let dateTimeOriginal = metadata.value(forMetadataKey: "Exif.Image.DateTimeOriginal",
  metadataType: .exif) as? String

Great success! 👍

I'll check out AlienSkin and FotoStation. Thanks for the tips!

@clanmills
Copy link
Collaborator

Thanks for the updates. Very pleased to learn that there's an Obj-C/swift/cocoa wrapper for Exiv2.

I hope to get the code for this issue into Exiv2 in the next few days. I'm off to visit family in the States next week and have a vacation to get away from the weather in England. When I get home (late February), I'll work on Exiv2 v0.27.1. The change for this issue will be in Exiv2 v0.27.1. Scheduled 2019-03-31.

Incidentally, there's a request to support FocusPositon from ARW files. The FocusPosition metadata in the Sony file is encrypted! (Why? Don't know. Phil helped me with the decryption code). Andreas' Tiff Engine supports decryption. With the knowledge I've gained from working on your issue, I hope to also fix FocusPosition in ARW.

@boardhead
Copy link
Collaborator

The Nikon maker note tag 0x00b7 (AFInfo2) data varies greatly by model. The latest ExifTool release (Jan 15) updates this decoding to add support for newer Nikon models (Z series). You can look at the "Condition" statements in the Exiftool source code to see which tags are valid under what conditions.

@mallman
Copy link
Collaborator Author

mallman commented Jan 18, 2019

When I get home (late February), I'll work on Exiv2 v0.27.1. The change for this issue will be in Exiv2 v0.27.1. Scheduled 2019-03-31.

Fantastic. Have a great break!

@clanmills
Copy link
Collaborator

diff --git a/src/makernote_int.cpp b/src/makernote_int.cpp
index 734d79bc..12c529a6 100644
--- a/src/makernote_int.cpp
+++ b/src/makernote_int.cpp
@@ -1185,6 +1185,16 @@ namespace Exiv2 {
         return aix == 0 ? -1 : aix->idx_;
     }
 
+    int nikonAf2Selector(uint16_t tag, const byte* /*pData*/, uint32_t size, TiffComponent* const /*pRoot*/)
+    {
+        int result = tag == 0x00b7 ? 0 : -1 ;
+        if (result > -1 && size == 84 ) {
+            result = 1;
+        }
+        std::cout << "nikonAf2Selector result = " << result << std::endl;
+        return result;
+    }
+
     DataBuf nikonCrypt(uint16_t tag, const byte* pData, uint32_t size, TiffComponent* const pRoot)
     {
         DataBuf buf;
diff --git a/src/makernote_int.hpp b/src/makernote_int.hpp
index a138d760..7fd4c43d 100644
--- a/src/makernote_int.hpp
+++ b/src/makernote_int.hpp
@@ -718,6 +718,17 @@ namespace Exiv2 {
     int nikonSelector(uint16_t tag, const byte* pData, uint32_t size, TiffComponent* const pRoot);
 
     /*!
+      @brief Function to select cfg + def of a Nikon complex binary array.
+
+      @param tag Tag number of the binary array
+      @param pData Pointer to the raw array data.
+      @param size Size of the array data.
+      @param pRoot Pointer to the root component of the TIFF tree.
+      @return An index into the array set, -1 if no match was found.
+     */
+     int nikonAf2Selector(uint16_t tag, const byte* pData, uint32_t size, TiffComponent* const pRoot);
+
+    /*!
       @brief Encrypt and decrypt Nikon data.
 
       Checks the version of the Nikon data array and en/decrypts (portions of) it as
diff --git a/src/nikonmn_int.cpp b/src/nikonmn_int.cpp
index f5efb578..3f105314 100644
--- a/src/nikonmn_int.cpp
+++ b/src/nikonmn_int.cpp
@@ -884,31 +884,57 @@ namespace Exiv2 {
         { 6, N_("On (105-point)")     }
     };
 
-    // Nikon3 Auto Focus Tag Info
-    const TagInfo Nikon3MakerNote::tagInfoAf2_[] = {
-        TagInfo(  0, "Version", N_("Version"), N_("Version"), nikonAf2Id, makerTags, undefined, 4, printExifVersion),
-        TagInfo(  4, "ContrastDetectAF", N_("Contrast Detect AF"), N_("Contrast detect AF"), nikonAf2Id, makerTags, unsignedByte, 1, EXV_PRINT_TAG(nikonOffOn)),
-        TagInfo(  5, "AFAreaMode", N_("AF Area Mode"), N_("AF area mode"), nikonAf2Id, makerTags, unsignedByte, 1, printValue),
-        TagInfo(  6, "PhaseDetectAF", N_("Phase Detect AF"), N_("Phase detect AF"), nikonAf2Id, makerTags, unsignedByte, 1, EXV_PRINT_TAG(nikonPhaseDetectAF)),
-        TagInfo(  7, "PrimaryAFPoint", N_("Primary AF Point"), N_("Primary AF point"), nikonAf2Id, makerTags, unsignedByte, 1, printValue),
-        TagInfo(  8, "AFPointsUsed", N_("AF Points Used"), N_("AF points used"), nikonAf2Id, makerTags, unsignedByte, 7, printValue),
-        TagInfo( 16, "AFImageWidth", N_("AF Image Width"), N_("AF image width"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
-        TagInfo( 18, "AFImageHeight", N_("AF Image Height"), N_("AF image height"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
-        TagInfo( 20, "AFAreaXPosition", N_("AF Area X Position"), N_("AF area x position"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
-        TagInfo( 22, "AFAreaYPosition", N_("AF Area Y Position"), N_("AF area y position"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
-        TagInfo( 24, "AFAreaWidth", N_("AF Area Width"), N_("AF area width"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
-        TagInfo( 26, "AFAreaHeight", N_("AF Area Height"), N_("AF area height"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
-        TagInfo( 28, "ContrastDetectAFInFocus", N_("Contrast Detect AF In Focus"), N_("Contrast detect AF in focus"), nikonAf2Id, makerTags, unsignedShort, 1, printValue),
-        // End of list marker
-        TagInfo(0xffff, "(UnknownNikonAf2Tag)", "(UnknownNikonAf2Tag)", N_("Unknown Nikon Auto Focus 2 Tag"), nikonAf2Id, makerTags, unsignedByte, 1, printValue)
-    };
 
-    const TagInfo* Nikon3MakerNote::tagListAf2()
-    {
-        return tagInfoAf2_;
-    }
+        // Nikon3 Auto Focus Tag Info
+        const TagInfo Nikon3MakerNote::tagInfoAf21_[] = {
+                TagInfo(  0, "Version", N_("Version"), N_("Version"), nikonAf21Id, makerTags, undefined, 4, printExifVersion),
+                TagInfo(  4, "ContrastDetectAF", N_("Contrast Detect AF"), N_("Contrast detect AF"), nikonAf21Id, makerTags, unsignedByte, 1, EXV_PRINT_TAG(nikonOffOn)),
+                TagInfo(  5, "AFAreaMode", N_("AF Area Mode"), N_("AF area mode"), nikonAf21Id, makerTags, unsignedByte, 1, printValue),
+                TagInfo(  6, "PhaseDetectAF", N_("Phase Detect AF"), N_("Phase detect AF"), nikonAf21Id, makerTags, unsignedByte, 1, EXV_PRINT_TAG(nikonPhaseDetectAF)),
+                TagInfo(  7, "PrimaryAFPoint", N_("Primary AF Point"), N_("Primary AF point"), nikonAf21Id, makerTags, unsignedByte, 1, printValue),
+                TagInfo(  8, "AFPointsUsed", N_("AF Points Used"), N_("AF points used"), nikonAf21Id, makerTags, unsignedByte, 7, printValue),
+                TagInfo( 16, "AFImageWidth", N_("AF Image Width"), N_("AF image width"), nikonAf21Id, makerTags, unsignedShort, 1, printValue),
+                TagInfo( 18, "AFImageHeight", N_("AF Image Height"), N_("AF image height"), nikonAf21Id, makerTags, unsignedShort, 1, printValue),
+                TagInfo( 20, "AFAreaXPosition", N_("AF Area X Position"), N_("AF area x position"), nikonAf21Id, makerTags, unsignedShort, 1, printValue),
+                TagInfo( 22, "AFAreaYPosition", N_("AF Area Y Position"), N_("AF area y position"), nikonAf21Id, makerTags, unsignedShort, 1, printValue),
+                TagInfo( 24, "AFAreaWidth", N_("AF Area Width"), N_("AF area width"), nikonAf21Id, makerTags, unsignedShort, 1, printValue),
+                TagInfo( 26, "AFAreaHeight", N_("AF Area Height"), N_("AF area height"), nikonAf21Id, makerTags, unsignedShort, 1, printValue),
+                TagInfo( 28, "ContrastDetectAFInFocus", N_("Contrast Detect AF In Focus"), N_("Contrast detect AF in focus"), nikonAf21Id, makerTags, unsignedShort, 1, printValue),
+                // End of list marker
+                TagInfo(0xffff, "(UnknownNikonAf2Tag)", "(UnknownNikonAf2Tag)", N_("Unknown Nikon Auto Focus 2 Tag"), nikonAf21Id, makerTags, unsignedByte, 1, printValue)
+        };
+
+        const TagInfo* Nikon3MakerNote::tagListAf21()
+        {
+            return tagInfoAf21_;
+        }
+
+        // Nikon3 Auto Focus Tag Info
+        const TagInfo Nikon3MakerNote::tagInfoAf22_[] = {
+                TagInfo(  0, "Version", N_("Version"), N_("Version"), nikonAf22Id, makerTags, undefined, 4, printExifVersion),
+                TagInfo(  4, "ContrastDetectAF", N_("Contrast Detect AF"), N_("Contrast detect AF"), nikonAf22Id, makerTags, unsignedByte, 1, EXV_PRINT_TAG(nikonOffOn)),
+                TagInfo(  5, "AFAreaMode", N_("AF Area Mode"), N_("AF area mode"), nikonAf22Id, makerTags, unsignedByte, 1, printValue),
+                TagInfo(  6, "PhaseDetectAF", N_("Phase Detect AF"), N_("Phase detect AF"), nikonAf22Id, makerTags, unsignedByte, 1, EXV_PRINT_TAG(nikonPhaseDetectAF)),
+                TagInfo(  7, "PrimaryAFPoint", N_("Primary AF Point"), N_("Primary AF point"), nikonAf22Id, makerTags, unsignedByte, 1, printValue),
+                TagInfo(  8, "AFPointsUsed", N_("AF Points Used"), N_("AF points used"), nikonAf22Id, makerTags, unsignedByte, 7, printValue),
+                TagInfo( 70, "AFImageWidth", N_("AF Image Width"), N_("AF image width"), nikonAf22Id, makerTags, unsignedShort, 1, printValue),
+                TagInfo( 72, "AFImageHeight", N_("AF Image Height"), N_("AF image height"), nikonAf22Id, makerTags, unsignedShort, 1, printValue),
+                TagInfo( 74, "AFAreaXPosition", N_("AF Area X Position"), N_("AF area x position"), nikonAf22Id, makerTags, unsignedShort, 1, printValue),
+                TagInfo( 76, "AFAreaYPosition", N_("AF Area Y Position"), N_("AF area y position"), nikonAf22Id, makerTags, unsignedShort, 1, printValue),
+                TagInfo( 78, "AFAreaWidth", N_("AF Area Width"), N_("AF area width"), nikonAf22Id, makerTags, unsignedShort, 1, printValue),
+                TagInfo( 80, "AFAreaHeight", N_("AF Area Height"), N_("AF area height"), nikonAf22Id, makerTags, unsignedShort, 1, printValue),
+                TagInfo( 82, "ContrastDetectAFInFocus", N_("Contrast Detect AF In Focus"), N_("Contrast detect AF in focus"), nikonAf22Id, makerTags, unsignedShort, 1, printValue),
+                // End of list marker
+                TagInfo(0xffff, "(UnknownNikonAf2Tag)", "(UnknownNikonAf2Tag)", N_("Unknown Nikon Auto Focus 2 Tag"), nikonAf22Id, makerTags, unsignedByte, 1, printValue)
+        };
+
+        const TagInfo* Nikon3MakerNote::tagListAf22()
+        {
+            return tagInfoAf22_;
+        }
+
 
-    // Nikon3 File Info Tag Info
+        // Nikon3 File Info Tag Info
     const TagInfo Nikon3MakerNote::tagInfoFi_[] = {
         TagInfo( 0, "Version", N_("Version"), N_("Version"), nikonFiId, makerTags, undefined, 4, printExifVersion),
         TagInfo( 6, "DirectoryNumber", N_("Directory Number"), N_("Directory number"), nikonFiId, makerTags, unsignedShort, 1, printValue),
diff --git a/src/nikonmn_int.hpp b/src/nikonmn_int.hpp
index 45a3cdbd..cdac89c0 100644
--- a/src/nikonmn_int.hpp
+++ b/src/nikonmn_int.hpp
@@ -124,7 +124,9 @@ namespace Exiv2 {
         //! Return read-only list of built-in Auto Focus tags
         static const TagInfo* tagListAf();
         //! Return read-only list of built-in Auto Focus 2 tags
-        static const TagInfo* tagListAf2();
+        static const TagInfo* tagListAf21();
+        //! Return read-only list of built-in Auto Focus 2 tags
+        static const TagInfo* tagListAf22();
         //! Return read-only list of built-in AF Fine Tune tags
         static const TagInfo* tagListAFT();
         //! Return read-only list of built-in File Info tags
@@ -235,7 +237,9 @@ namespace Exiv2 {
         //! Auto Focus tag information
         static const TagInfo tagInfoAf_[];
         //! Auto Focus tag 2 information
-        static const TagInfo tagInfoAf2_[];
+        static const TagInfo tagInfoAf21_[];
+        //! Auto Focus tag 2 information
+        static const TagInfo tagInfoAf22_[];
         //! AF Fine Tune tag information
         static const TagInfo tagInfoAFT_[];
         //! File Info tag information
diff --git a/src/tags_int.cpp b/src/tags_int.cpp
index 918cd8fe..c8854a56 100644
--- a/src/tags_int.cpp
+++ b/src/tags_int.cpp
@@ -106,7 +106,8 @@ namespace Exiv2 {
         { nikonWtId,       "Makernote", "NikonWt",      Nikon3MakerNote::tagListWt     },
         { nikonIiId,       "Makernote", "NikonIi",      Nikon3MakerNote::tagListIi     },
         { nikonAfId,       "Makernote", "NikonAf",      Nikon3MakerNote::tagListAf     },
-        { nikonAf2Id,      "Makernote", "NikonAf2",     Nikon3MakerNote::tagListAf2    },
+        { nikonAf21Id,     "Makernote", "NikonAf21",    Nikon3MakerNote::tagListAf21   },
+        { nikonAf22Id,     "Makernote", "NikonAf22",    Nikon3MakerNote::tagListAf22   },
         { nikonAFTId,      "Makernote", "NikonAFT",     Nikon3MakerNote::tagListAFT    },
         { nikonFiId,       "Makernote", "NikonFi",      Nikon3MakerNote::tagListFi     },
         { nikonMeId,       "Makernote", "NikonMe",      Nikon3MakerNote::tagListMe     },
diff --git a/src/tags_int.hpp b/src/tags_int.hpp
index 91337e5b..eaa8baf9 100644
--- a/src/tags_int.hpp
+++ b/src/tags_int.hpp
@@ -99,7 +99,8 @@ namespace Exiv2 {
         nikonWtId,
         nikonIiId,
         nikonAfId,
-        nikonAf2Id,
+        nikonAf21Id,
+        nikonAf22Id,
         nikonAFTId,
         nikonFiId,
         nikonMeId,
diff --git a/src/tiffcomposite_int.cpp b/src/tiffcomposite_int.cpp
index 5320c3e4..cab30453 100644
--- a/src/tiffcomposite_int.cpp
+++ b/src/tiffcomposite_int.cpp
@@ -952,9 +952,23 @@ namespace Exiv2 {
 
     void TiffBinaryArray::doAccept(TiffVisitor& visitor)
     {
+        static int counter = 0 ;
+        size_t     before_size    = this->elements_.size();
+        int        before_counter = ++counter ;
+
+        if ( before_counter == 88 ) {
+            std::cout << "TiffBinaryArray::doAccept - before_counter = " << before_counter << std::endl;
+        }
         visitor.visitBinaryArray(this);
+        if ( before_size  != this->elements_.size() ) {
+            std::cout << "TiffBinaryArray::doAccept  before_counter = " << before_counter
+                      << " size,group = " << this->size()    <<","<< this->group()
+                      << " before, after = " << before_size  <<","<< this->elements_.size()
+                      << std::endl;
+        }
         for (Components::const_iterator i = elements_.begin();
              visitor.go(TiffVisitor::geTraverse) && i != elements_.end(); ++i) {
+             // std::cout << "TiffBinaryArray::doAccept tag, group = " << (*i)->tag() <<","<< (*i)->group() << std::endl;
             (*i)->accept(visitor);
         }
         if (visitor.go(TiffVisitor::geTraverse)) visitor.visitBinaryArrayEnd(this);
diff --git a/src/tiffimage_int.cpp b/src/tiffimage_int.cpp
index d899afdc..0b6d9eab 100644
--- a/src/tiffimage_int.cpp
+++ b/src/tiffimage_int.cpp
@@ -222,8 +222,8 @@ namespace Exiv2 {
     };
 
     //! Nikon Auto Focus 2 binary array - configuration
-    extern const ArrayCfg nikonAf2Cfg = {
-        nikonAf2Id,       // Group for the elements
+    extern const ArrayCfg nikonAf21Cfg = {
+        nikonAf21Id,      // Group for the elements
         littleEndian,     // Byte order
         ttUndefined,      // Type for array entry
         notEncrypted,     // Not encrypted
@@ -233,7 +233,7 @@ namespace Exiv2 {
         { 0, ttUnsignedByte,  1 }
     };
     //! Nikon Auto Focus 2 binary array - definition
-    extern const ArrayDef nikonAf2Def[] = {
+    extern const ArrayDef nikonAf21Def[] = {
         {  0, ttUndefined,     4 }, // Version
         {  4, ttUnsignedByte,  1 }, // ContrastDetectAF
         {  5, ttUnsignedByte,  1 }, // AFAreaMode
@@ -249,6 +249,40 @@ namespace Exiv2 {
         { 28, ttUnsignedShort, 1 }, // ContrastDetectAFInFocus
     };
 
+    //! Nikon Auto Focus 2 binary array - configuration
+    extern const ArrayCfg nikonAf22Cfg = {
+        nikonAf22Id,      // Group for the elements
+        littleEndian,     // Byte order
+        ttUndefined,      // Type for array entry
+        notEncrypted,     // Not encrypted
+        false,            // No size element
+        true,             // Write all tags
+        true,            // Concatenate gaps
+        { 0, ttUnsignedByte,  1 }
+    };
+    //! Nikon Auto Focus 2 binary array - definition
+    extern const ArrayDef nikonAf22Def[] = {
+        {  0, ttUndefined,     4 }, // Version
+        {  4, ttUnsignedByte,  1 }, // ContrastDetectAF
+        {  5, ttUnsignedByte,  1 }, // AFAreaMode
+        {  6, ttUnsignedByte,  1 }, // PhaseDetectAF
+        {  7, ttUnsignedByte,  1 }, // PrimaryAFPoint
+        {  8, ttUnsignedByte,  7 }, // AFPointsUsed
+        { 70, ttUnsignedShort, 1 }, // AFImageWidth
+        { 72, ttUnsignedShort, 1 }, // AFImageHeight
+        { 74, ttUnsignedShort, 1 }, // AFAreaXPosition
+        { 76, ttUnsignedShort, 1 }, // AFAreaYPosition
+        { 78, ttUnsignedShort, 1 }, // AFAreaWidth
+        { 80, ttUnsignedShort, 1 }, // AFAreaHeight
+    };
+
+    //! Nikon AF2 configuration and definitions
+    //  https://github.com/Exiv2/exiv2/issues/646
+    extern const ArraySet nikonAf2Set[] = {
+        { nikonAf21Cfg, nikonAf21Def, EXV_COUNTOF(nikonAf21Def) },
+        { nikonAf22Cfg, nikonAf22Def, EXV_COUNTOF(nikonAf22Def) },
+    };
+
     //! Nikon AF Fine Tune binary array - configuration
     extern const ArrayCfg nikonAFTCfg = {
         nikonAFTId,       // Group for the elements
@@ -932,7 +966,8 @@ namespace Exiv2 {
         { Tag::root, nikonLd2Id,       nikon3Id,         0x0098    },
         { Tag::root, nikonLd3Id,       nikon3Id,         0x0098    },
         { Tag::root, nikonMeId,        nikon3Id,         0x00b0    },
-        { Tag::root, nikonAf2Id,       nikon3Id,         0x00b7    },
+        { Tag::root, nikonAf21Id,      nikon3Id,         0x00b7    },
+        { Tag::root, nikonAf22Id,      nikon3Id,         0x00b7    },
         { Tag::root, nikonFiId,        nikon3Id,         0x00b8    },
         { Tag::root, nikonAFTId,       nikon3Id,         0x00b9    },
         { Tag::root, nikonFl1Id,       nikon3Id,         0x00a8    },
@@ -1274,7 +1309,8 @@ namespace Exiv2 {
         {    0x0098, nikon3Id,         EXV_COMPLEX_BINARY_ARRAY(nikonLdSet, nikonSelector) },
         {    0x00a8, nikon3Id,         EXV_COMPLEX_BINARY_ARRAY(nikonFlSet, nikonSelector) },
         {    0x00b0, nikon3Id,         EXV_BINARY_ARRAY(nikonMeCfg, nikonMeDef)  },
-        {    0x00b7, nikon3Id,         EXV_BINARY_ARRAY(nikonAf2Cfg, nikonAf2Def)},
+        {    0x00b7, nikon3Id,         EXV_COMPLEX_BINARY_ARRAY(nikonAf2Set, nikonAf2Selector) },
+        // {    0x00b7, nikon3Id,         EXV_BINARY_ARRAY(nikonAf22Cfg, nikonAf22Def)},
         {    0x00b8, nikon3Id,         EXV_BINARY_ARRAY(nikonFiCfg, nikonFiDef)  },
         {    0x00b9, nikon3Id,         EXV_BINARY_ARRAY(nikonAFTCfg, nikonAFTDef)  },
         {  Tag::all, nikon3Id,         newTiffEntry                              },
@@ -1301,7 +1337,8 @@ namespace Exiv2 {
         {  Tag::all, nikonAfId,        newTiffBinaryElement                      },
 
         // Nikon3 auto focus 2
-        {  Tag::all, nikonAf2Id,       newTiffBinaryElement                      },
+        {  Tag::all, nikonAf21Id,      newTiffBinaryElement                      },
+        {  Tag::all, nikonAf22Id,      newTiffBinaryElement                      },
 
         // Nikon3 AF Fine Tune
         {  Tag::all, nikonAFTId,       newTiffBinaryElement                      },
diff --git a/src/tiffvisitor_int.cpp b/src/tiffvisitor_int.cpp
index 1cec02c5..e3d2d8e5 100644
--- a/src/tiffvisitor_int.cpp
+++ b/src/tiffvisitor_int.cpp
@@ -473,7 +473,16 @@ namespace Exiv2 {
                                                       object->group());
         // skip decoding if decoderFct == 0
         if (decoderFct) {
-            EXV_CALL_MEMBER_FN(*this, decoderFct)(object);
+            if ( object->group() == nikonAf22Id ) {
+                std::cout << "decodeTiffEntry tag = " << object->tag()
+                          << " group = "       << object->group()
+                          << " make_ = "       << make_
+                          << std::endl
+                          ;
+                EXV_CALL_MEMBER_FN(*this, decoderFct)(object);
+            } else {
+                EXV_CALL_MEMBER_FN(*this, decoderFct)(object);
+            }
         }
     } // TiffDecoder::decodeTiffEntry
 
@@ -482,6 +491,7 @@ namespace Exiv2 {
         assert(object != 0);
         ExifKey key(object->tag(), groupName(object->group()));
         key.setIdx(object->idx());
+        std::cout << "TiffDecoder::decodeStdTiffEntry adding: " << key.tagName() << std::endl;
         exifData_.add(key, object->pValue());
 
     } // TiffDecoder::decodeTiffEntry
@@ -1250,6 +1260,7 @@ namespace Exiv2 {
         setMnState(); // All components to be post-processed must be from the Makernote
         postProc_ = true;
         for (PostList::const_iterator pos = postList_.begin(); pos != postList_.end(); ++pos) {
+            std::cout << "TiffReader::postProcess " << " tag,group = " << (*pos)->tag()  <<","<< (*pos)->group() << std::endl;
             (*pos)->accept(*this);
         }
         postProc_ = false;
@@ -1480,6 +1491,9 @@ namespace Exiv2 {
         }
         p += 2;
         uint32_t count = getULong(p, byteOrder());
+        if ( count == 84 ) {
+            std::cout << "count == 84" << std::endl;
+        }
         if (count >= 0x10000000) {
 #ifndef SUPPRESS_WARNINGS
             EXV_ERROR << "Directory " << groupName(object->group())
@@ -1569,6 +1583,7 @@ namespace Exiv2 {
         object->setData(pData, size);
         object->setOffset(offset);
         object->setIdx(nextIdx(object->group()));
+        // std::cout << "stored tag, group, size = " << object->tag() <<","<< object->group() <<","<< object->size() << std::endl;
 
     } // TiffReader::readTiffEntry
 
@@ -1582,9 +1597,10 @@ namespace Exiv2 {
             readTiffEntry(object);
             object->iniOrigDataBuf();
             postList_.push_back(object);
+            // std::cout << "pushed tag,group = "  << object->tag() << "," << object->group() << " size = " << object->size_ << std::endl;
             return;
         }
-
+        // std::cout << "TiffReader::visitBinaryArray tag,group = " << object->tag() << "," << object->group() << std::endl;
         // Check duplicates
         TiffFinder finder(object->tag(), object->group());
         pRoot_->accept(finder);
@@ -1619,6 +1635,8 @@ namespace Exiv2 {
         const ArrayDef* def = &cfg->elDefaultDef_;
         ArrayDef gap = *def;
 
+        bool b84 =  object->size() == 84;
+
         for (uint32_t idx = 0; idx < object->TiffEntryBase::doSize(); ) {
             if (defs) {
                 def = std::find(defs, defsEnd, idx);
@@ -1648,6 +1666,9 @@ namespace Exiv2 {
                     }
                 }
             }
+            if ( b84 ) {
+                std::cout << "TiffReader::visitBinaryArray addElement idx=" << idx << std::endl;
+            }
             idx += object->addElement(idx, *def); // idx may be different from def->idx_
         }
 

@piponazo
Copy link
Collaborator

@clanmills can you send me the image @mallman sent you to your personal email, so that I can continue with the research ?

@mallman
Copy link
Collaborator Author

mallman commented Mar 24, 2019

@clanmills Hi Robin. Please do not share that file. Instead, we can share the following raw file publicly:

https://www.dropbox.com/s/sh1pt9na74an69k/_DSC6043.NEF?dl=0

@clanmills
Copy link
Collaborator

NP. Deleted.

@clanmills clanmills removed this from the v0.27.1 milestone Apr 12, 2019
@clanmills clanmills added this to the v0.27.2 milestone Apr 12, 2019
@clanmills clanmills self-assigned this May 18, 2019
@clanmills
Copy link
Collaborator

PR Submitted: #900

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants