Skip to content

Commit e8638b6

Browse files
authored
Merge pull request #341 from drewnoakes/nikon-picture-control
Improve Nikon makernote support
2 parents 7c5d3f0 + 8f3d671 commit e8638b6

14 files changed

+573
-47
lines changed

MetadataExtractor/Formats/Exif/ExifTiffHandler.cs

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,7 @@ public override bool CustomProcessTag(in TiffReaderContext context, int tagId, i
258258
return true;
259259
}
260260
}
261-
262-
if (CurrentDirectory is PanasonicRawIfd0Directory)
261+
else if (CurrentDirectory is PanasonicRawIfd0Directory)
263262
{
264263
// these contain binary data with specific offsets, and can't be processed as regular IFD's.
265264
// The binary data is broken into 'fake' tags and there is a pattern.
@@ -282,6 +281,22 @@ public override bool CustomProcessTag(in TiffReaderContext context, int tagId, i
282281
return true;
283282
}
284283

284+
// Panasonic RAW sometimes contains an embedded version of the data as a JPG file.
285+
if (tagId == PanasonicRawIfd0Directory.TagJpgFromRaw)
286+
{
287+
// Extract information from embedded image since it is metadata-rich
288+
var bytes = context.Reader.GetBytes(valueOffset, byteCount);
289+
var stream = new MemoryStream(bytes);
290+
291+
foreach (var directory in JpegMetadataReader.ReadMetadata(stream))
292+
{
293+
directory.Parent = CurrentDirectory;
294+
Directories.Add(directory);
295+
}
296+
297+
return true;
298+
}
299+
285300
static void ProcessBinary(Directory directory, int tagValueOffset, IndexedReader reader, int byteCount, bool isSigned, int arrayLength)
286301
{
287302
// expects signed/unsigned int16 (for now)
@@ -326,20 +341,28 @@ static void ProcessBinary(Directory directory, int tagValueOffset, IndexedReader
326341
}
327342
}
328343

329-
// Panasonic RAW sometimes contains an embedded version of the data as a JPG file.
330-
if (tagId == PanasonicRawIfd0Directory.TagJpgFromRaw && CurrentDirectory is PanasonicRawIfd0Directory)
344+
if (tagId is NikonType2MakernoteDirectory.TagPictureControl or NikonType2MakernoteDirectory.TagPictureControl2 && CurrentDirectory is NikonType2MakernoteDirectory)
331345
{
332-
// Extract information from embedded image since it is metadata-rich
333-
var bytes = context.Reader.GetBytes(valueOffset, byteCount);
334-
var stream = new MemoryStream(bytes);
335-
336-
foreach (var directory in JpegMetadataReader.ReadMetadata(stream))
346+
if (byteCount == 58)
337347
{
348+
var bytes = context.Reader.GetBytes(valueOffset, byteCount);
349+
var directory = NikonPictureControl1Directory.FromBytes(bytes);
338350
directory.Parent = CurrentDirectory;
339351
Directories.Add(directory);
352+
return true;
353+
}
354+
else if (byteCount == 68)
355+
{
356+
var bytes = context.Reader.GetBytes(valueOffset, byteCount);
357+
var directory = NikonPictureControl2Directory.FromBytes(bytes);
358+
directory.Parent = CurrentDirectory;
359+
Directories.Add(directory);
360+
return true;
361+
}
362+
else if (byteCount == 74)
363+
{
364+
// TODO NikonPictureControl3Directory
340365
}
341-
342-
return true;
343366
}
344367

345368
return false;
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
2+
3+
using static MetadataExtractor.Formats.Exif.Makernotes.NikonPictureControl1Directory;
4+
5+
namespace MetadataExtractor.Formats.Exif.Makernotes;
6+
7+
public sealed class NikonPictureControl1Descriptor : TagDescriptor<NikonPictureControl1Directory>
8+
{
9+
public NikonPictureControl1Descriptor(NikonPictureControl1Directory directory)
10+
: base(directory)
11+
{
12+
}
13+
14+
public override string? GetDescription(int tagType)
15+
{
16+
return tagType switch
17+
{
18+
TagPictureControlAdjust => GetPictureControlAdjustDescription(),
19+
TagFilterEffect => GetFilterEffectDescription(),
20+
TagToningEffect => GetToningEffectDescription(),
21+
_ => base.GetDescription(tagType)
22+
};
23+
}
24+
25+
public string? GetPictureControlAdjustDescription()
26+
{
27+
return GetIndexedDescription(
28+
TagPictureControlAdjust,
29+
"Default Settings",
30+
"Quick Adjust",
31+
"Full Control");
32+
}
33+
34+
public string? GetFilterEffectDescription()
35+
{
36+
if (!Directory.TryGetByte(TagFilterEffect, out byte value))
37+
return null;
38+
39+
return value switch
40+
{
41+
0x80 => "Off",
42+
0x81 => "Yellow",
43+
0x82 => "Orange",
44+
0x83 => "Red",
45+
0x84 => "Green",
46+
0xFF => "N/A",
47+
_ => base.GetDescription(TagFilterEffect)
48+
};
49+
}
50+
51+
public string? GetToningEffectDescription()
52+
{
53+
if (!Directory.TryGetByte(TagToningEffect, out byte value))
54+
return null;
55+
56+
return value switch
57+
{
58+
0x80 => "B&W",
59+
0x81 => "Sepia",
60+
0x82 => "Cyanotype",
61+
0x83 => "Red",
62+
0x84 => "Yellow",
63+
0x85 => "Green",
64+
0x86 => "Blue-green",
65+
0x87 => "Blue",
66+
0x88 => "Purple-blue",
67+
0x89 => "Red-purple",
68+
0xFF => "N/A",
69+
_ => base.GetDescription(TagToningEffect)
70+
};
71+
}
72+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
2+
3+
namespace MetadataExtractor.Formats.Exif.Makernotes;
4+
5+
public sealed class NikonPictureControl1Directory : Directory
6+
{
7+
// Tag values are offsets into the underlying data.
8+
// Data from https://exiftool.org/TagNames/Nikon.html#PictureControl
9+
10+
public const int TagPictureControlVersion = 0;
11+
public const int TagPictureControlName = 4;
12+
public const int TagPictureControlBase = 24;
13+
// skip 4
14+
public const int TagPictureControlAdjust = 48;
15+
public const int TagPictureControlQuickAdjust = 49;
16+
public const int TagSharpness = 50;
17+
public const int TagContrast = 51;
18+
public const int TagBrightness = 52;
19+
public const int TagSaturation = 53;
20+
public const int TagHueAdjustment = 54;
21+
public const int TagFilterEffect = 55;
22+
public const int TagToningEffect = 56;
23+
public const int TagToningSaturation = 57;
24+
25+
private static readonly Dictionary<int, string> _tagNameMap = new()
26+
{
27+
{ TagPictureControlVersion, "Picture Control Version" },
28+
{ TagPictureControlName, "Picture Control Name" },
29+
{ TagPictureControlBase, "Picture Control Base" },
30+
{ TagPictureControlAdjust, "Picture Control Adjust" },
31+
{ TagPictureControlQuickAdjust, "Picture Control Quick Adjust" },
32+
{ TagSharpness, "Sharpness" },
33+
{ TagContrast, "Contrast" },
34+
{ TagBrightness, "Brightness" },
35+
{ TagSaturation, "Saturation" },
36+
{ TagHueAdjustment, "Hue Adjustment" },
37+
{ TagFilterEffect, "Filter Effect" },
38+
{ TagToningEffect, "Toning Effect" },
39+
{ TagToningSaturation, "Toning Saturation" },
40+
};
41+
42+
public NikonPictureControl1Directory() : base(_tagNameMap)
43+
{
44+
SetDescriptor(new NikonPictureControl1Descriptor(this));
45+
}
46+
47+
public override string Name => "Nikon PictureControl 1";
48+
49+
internal static NikonPictureControl1Directory FromBytes(byte[] bytes)
50+
{
51+
const int ExpectedLength = 58;
52+
53+
if (bytes.Length != ExpectedLength)
54+
{
55+
throw new ArgumentException($"Must have {ExpectedLength} bytes.");
56+
}
57+
58+
SequentialByteArrayReader reader = new(bytes);
59+
60+
NikonPictureControl1Directory directory = new();
61+
62+
directory.Set(TagPictureControlVersion, reader.GetStringValue(4));
63+
directory.Set(TagPictureControlName, reader.GetStringValue(20));
64+
directory.Set(TagPictureControlBase, reader.GetStringValue(20));
65+
reader.Skip(4);
66+
directory.Set(TagPictureControlAdjust, reader.GetByte());
67+
directory.Set(TagPictureControlQuickAdjust, reader.GetByte());
68+
directory.Set(TagSharpness, reader.GetByte());
69+
directory.Set(TagContrast, reader.GetByte());
70+
directory.Set(TagBrightness, reader.GetByte());
71+
directory.Set(TagSaturation, reader.GetByte());
72+
directory.Set(TagHueAdjustment, reader.GetByte());
73+
directory.Set(TagFilterEffect, reader.GetByte());
74+
directory.Set(TagToningEffect, reader.GetByte());
75+
directory.Set(TagToningSaturation, reader.GetByte());
76+
77+
Debug.Assert(reader.Position == ExpectedLength);
78+
79+
return directory;
80+
}
81+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
2+
3+
using static MetadataExtractor.Formats.Exif.Makernotes.NikonPictureControl2Directory;
4+
5+
namespace MetadataExtractor.Formats.Exif.Makernotes;
6+
7+
public sealed class NikonPictureControl2Descriptor : TagDescriptor<NikonPictureControl2Directory>
8+
{
9+
public NikonPictureControl2Descriptor(NikonPictureControl2Directory directory)
10+
: base(directory)
11+
{
12+
}
13+
14+
public override string? GetDescription(int tagType)
15+
{
16+
return tagType switch
17+
{
18+
TagPictureControlAdjust => GetPictureControlAdjustDescription(),
19+
TagFilterEffect => GetFilterEffectDescription(),
20+
TagToningEffect => GetToningEffectDescription(),
21+
_ => base.GetDescription(tagType)
22+
};
23+
}
24+
25+
public string? GetPictureControlAdjustDescription()
26+
{
27+
return GetIndexedDescription(
28+
TagPictureControlAdjust,
29+
"Default Settings",
30+
"Quick Adjust",
31+
"Full Control");
32+
}
33+
34+
public string? GetFilterEffectDescription()
35+
{
36+
if (!Directory.TryGetByte(TagFilterEffect, out byte value))
37+
return null;
38+
39+
return value switch
40+
{
41+
0x80 => "Off",
42+
0x81 => "Yellow",
43+
0x82 => "Orange",
44+
0x83 => "Red",
45+
0x84 => "Green",
46+
0xFF => "N/A",
47+
_ => base.GetDescription(TagFilterEffect)
48+
};
49+
}
50+
51+
public string? GetToningEffectDescription()
52+
{
53+
if (!Directory.TryGetByte(TagToningEffect, out byte value))
54+
return null;
55+
56+
return value switch
57+
{
58+
0x80 => "B&W",
59+
0x81 => "Sepia",
60+
0x82 => "Cyanotype",
61+
0x83 => "Red",
62+
0x84 => "Yellow",
63+
0x85 => "Green",
64+
0x86 => "Blue-green",
65+
0x87 => "Blue",
66+
0x88 => "Purple-blue",
67+
0x89 => "Red-purple",
68+
0xFF => "N/A",
69+
_ => base.GetDescription(TagToningEffect)
70+
};
71+
}
72+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
2+
3+
namespace MetadataExtractor.Formats.Exif.Makernotes;
4+
5+
public sealed class NikonPictureControl2Directory : Directory
6+
{
7+
// Tag values are offsets into the underlying data.
8+
// Data from https://exiftool.org/TagNames/Nikon.html#PictureControl
9+
10+
public const int TagPictureControlVersion = 0;
11+
public const int TagPictureControlName = 4;
12+
public const int TagPictureControlBase = 24;
13+
public const int TagPictureControlAdjust = 48;
14+
public const int TagPictureControlQuickAdjust = 49;
15+
// skip 1
16+
public const int TagSharpness = 51;
17+
// skip 1
18+
public const int TagClarity = 53;
19+
// skip 1
20+
public const int TagContrast = 55;
21+
// skip 1
22+
public const int TagBrightness = 57;
23+
// skip 1
24+
public const int TagSaturation = 59;
25+
// skip 1
26+
public const int TagHue = 61;
27+
// skip 1
28+
public const int TagFilterEffect = 63;
29+
public const int TagToningEffect = 64;
30+
public const int TagToningSaturation = 65;
31+
32+
private static readonly Dictionary<int, string> _tagNameMap = new()
33+
{
34+
{ TagPictureControlVersion, "Picture Control Version" },
35+
{ TagPictureControlName, "Picture Control Name" },
36+
{ TagPictureControlBase, "Picture Control Base" },
37+
{ TagPictureControlAdjust, "Picture Control Adjust" },
38+
{ TagPictureControlQuickAdjust, "Picture Control Quick Adjust" },
39+
{ TagSharpness, "Sharpness" },
40+
{ TagClarity, "Clarity" },
41+
{ TagContrast, "Contrast" },
42+
{ TagBrightness, "Brightness" },
43+
{ TagSaturation, "Saturation" },
44+
{ TagHue, "Hue" },
45+
{ TagFilterEffect, "Filter Effect" },
46+
{ TagToningEffect, "Toning Effect" },
47+
{ TagToningSaturation, "Toning Saturation" },
48+
};
49+
50+
public NikonPictureControl2Directory() : base(_tagNameMap)
51+
{
52+
SetDescriptor(new NikonPictureControl2Descriptor(this));
53+
}
54+
55+
public override string Name => "Nikon PictureControl 2";
56+
57+
internal static NikonPictureControl2Directory FromBytes(byte[] bytes)
58+
{
59+
const int ExpectedLength = 68;
60+
61+
if (bytes.Length != ExpectedLength)
62+
{
63+
throw new ArgumentException($"Must have {ExpectedLength} bytes.");
64+
}
65+
66+
SequentialByteArrayReader reader = new(bytes);
67+
68+
NikonPictureControl2Directory directory = new();
69+
70+
directory.Set(TagPictureControlVersion, reader.GetStringValue(4));
71+
directory.Set(TagPictureControlName, reader.GetStringValue(20));
72+
directory.Set(TagPictureControlBase, reader.GetStringValue(20));
73+
reader.Skip(4);
74+
directory.Set(TagPictureControlAdjust, reader.GetByte());
75+
directory.Set(TagPictureControlQuickAdjust, reader.GetByte());
76+
reader.Skip(1);
77+
directory.Set(TagSharpness, reader.GetByte());
78+
reader.Skip(1);
79+
directory.Set(TagClarity, reader.GetByte());
80+
reader.Skip(1);
81+
directory.Set(TagContrast, reader.GetByte());
82+
reader.Skip(1);
83+
directory.Set(TagBrightness, reader.GetByte());
84+
reader.Skip(1);
85+
directory.Set(TagSaturation, reader.GetByte());
86+
reader.Skip(1);
87+
directory.Set(TagHue, reader.GetByte());
88+
reader.Skip(1);
89+
directory.Set(TagFilterEffect, reader.GetByte());
90+
directory.Set(TagToningEffect, reader.GetByte());
91+
directory.Set(TagToningSaturation, reader.GetByte());
92+
reader.Skip(2);
93+
94+
Debug.Assert(reader.Position == ExpectedLength);
95+
96+
return directory;
97+
}
98+
}

0 commit comments

Comments
 (0)