Skip to content

Improve Nikon makernote support #341

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

Merged
merged 4 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 34 additions & 11 deletions MetadataExtractor/Formats/Exif/ExifTiffHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,7 @@ public override bool CustomProcessTag(in TiffReaderContext context, int tagId, i
return true;
}
}

if (CurrentDirectory is PanasonicRawIfd0Directory)
else if (CurrentDirectory is PanasonicRawIfd0Directory)
{
// these contain binary data with specific offsets, and can't be processed as regular IFD's.
// The binary data is broken into 'fake' tags and there is a pattern.
Expand All @@ -282,6 +281,22 @@ public override bool CustomProcessTag(in TiffReaderContext context, int tagId, i
return true;
}

// Panasonic RAW sometimes contains an embedded version of the data as a JPG file.
if (tagId == PanasonicRawIfd0Directory.TagJpgFromRaw)
{
// Extract information from embedded image since it is metadata-rich
var bytes = context.Reader.GetBytes(valueOffset, byteCount);
var stream = new MemoryStream(bytes);

foreach (var directory in JpegMetadataReader.ReadMetadata(stream))
{
directory.Parent = CurrentDirectory;
Directories.Add(directory);
}

return true;
}

static void ProcessBinary(Directory directory, int tagValueOffset, IndexedReader reader, int byteCount, bool isSigned, int arrayLength)
{
// expects signed/unsigned int16 (for now)
Expand Down Expand Up @@ -326,20 +341,28 @@ static void ProcessBinary(Directory directory, int tagValueOffset, IndexedReader
}
}

// Panasonic RAW sometimes contains an embedded version of the data as a JPG file.
if (tagId == PanasonicRawIfd0Directory.TagJpgFromRaw && CurrentDirectory is PanasonicRawIfd0Directory)
if (tagId is NikonType2MakernoteDirectory.TagPictureControl or NikonType2MakernoteDirectory.TagPictureControl2 && CurrentDirectory is NikonType2MakernoteDirectory)
{
// Extract information from embedded image since it is metadata-rich
var bytes = context.Reader.GetBytes(valueOffset, byteCount);
var stream = new MemoryStream(bytes);

foreach (var directory in JpegMetadataReader.ReadMetadata(stream))
if (byteCount == 58)
{
var bytes = context.Reader.GetBytes(valueOffset, byteCount);
var directory = NikonPictureControl1Directory.FromBytes(bytes);
directory.Parent = CurrentDirectory;
Directories.Add(directory);
return true;
}
else if (byteCount == 68)
{
var bytes = context.Reader.GetBytes(valueOffset, byteCount);
var directory = NikonPictureControl2Directory.FromBytes(bytes);
directory.Parent = CurrentDirectory;
Directories.Add(directory);
return true;
}
else if (byteCount == 74)
{
// TODO NikonPictureControl3Directory
}

return true;
}

return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// 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.

using static MetadataExtractor.Formats.Exif.Makernotes.NikonPictureControl1Directory;

namespace MetadataExtractor.Formats.Exif.Makernotes;

public sealed class NikonPictureControl1Descriptor : TagDescriptor<NikonPictureControl1Directory>
{
public NikonPictureControl1Descriptor(NikonPictureControl1Directory directory)
: base(directory)
{
}

public override string? GetDescription(int tagType)
{
return tagType switch
{
TagPictureControlAdjust => GetPictureControlAdjustDescription(),
TagFilterEffect => GetFilterEffectDescription(),
TagToningEffect => GetToningEffectDescription(),
_ => base.GetDescription(tagType)
};
}

public string? GetPictureControlAdjustDescription()
{
return GetIndexedDescription(
TagPictureControlAdjust,
"Default Settings",
"Quick Adjust",
"Full Control");
}

public string? GetFilterEffectDescription()
{
if (!Directory.TryGetByte(TagFilterEffect, out byte value))
return null;

return value switch
{
0x80 => "Off",
0x81 => "Yellow",
0x82 => "Orange",
0x83 => "Red",
0x84 => "Green",
0xFF => "N/A",
_ => base.GetDescription(TagFilterEffect)
};
}

public string? GetToningEffectDescription()
{
if (!Directory.TryGetByte(TagToningEffect, out byte value))
return null;

return value switch
{
0x80 => "B&W",
0x81 => "Sepia",
0x82 => "Cyanotype",
0x83 => "Red",
0x84 => "Yellow",
0x85 => "Green",
0x86 => "Blue-green",
0x87 => "Blue",
0x88 => "Purple-blue",
0x89 => "Red-purple",
0xFF => "N/A",
_ => base.GetDescription(TagToningEffect)
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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.

namespace MetadataExtractor.Formats.Exif.Makernotes;

public sealed class NikonPictureControl1Directory : Directory
{
// Tag values are offsets into the underlying data.
// Data from https://exiftool.org/TagNames/Nikon.html#PictureControl

public const int TagPictureControlVersion = 0;
public const int TagPictureControlName = 4;
public const int TagPictureControlBase = 24;
// skip 4
public const int TagPictureControlAdjust = 48;
public const int TagPictureControlQuickAdjust = 49;
public const int TagSharpness = 50;
public const int TagContrast = 51;
public const int TagBrightness = 52;
public const int TagSaturation = 53;
public const int TagHueAdjustment = 54;
public const int TagFilterEffect = 55;
public const int TagToningEffect = 56;
public const int TagToningSaturation = 57;

private static readonly Dictionary<int, string> _tagNameMap = new()
{
{ TagPictureControlVersion, "Picture Control Version" },
{ TagPictureControlName, "Picture Control Name" },
{ TagPictureControlBase, "Picture Control Base" },
{ TagPictureControlAdjust, "Picture Control Adjust" },
{ TagPictureControlQuickAdjust, "Picture Control Quick Adjust" },
{ TagSharpness, "Sharpness" },
{ TagContrast, "Contrast" },
{ TagBrightness, "Brightness" },
{ TagSaturation, "Saturation" },
{ TagHueAdjustment, "Hue Adjustment" },
{ TagFilterEffect, "Filter Effect" },
{ TagToningEffect, "Toning Effect" },
{ TagToningSaturation, "Toning Saturation" },
};

public NikonPictureControl1Directory() : base(_tagNameMap)
{
SetDescriptor(new NikonPictureControl1Descriptor(this));
}

public override string Name => "Nikon PictureControl 1";

internal static NikonPictureControl1Directory FromBytes(byte[] bytes)
{
const int ExpectedLength = 58;

if (bytes.Length != ExpectedLength)
{
throw new ArgumentException($"Must have {ExpectedLength} bytes.");
}

SequentialByteArrayReader reader = new(bytes);

NikonPictureControl1Directory directory = new();

directory.Set(TagPictureControlVersion, reader.GetStringValue(4));
directory.Set(TagPictureControlName, reader.GetStringValue(20));
directory.Set(TagPictureControlBase, reader.GetStringValue(20));
reader.Skip(4);
directory.Set(TagPictureControlAdjust, reader.GetByte());
directory.Set(TagPictureControlQuickAdjust, reader.GetByte());
directory.Set(TagSharpness, reader.GetByte());
directory.Set(TagContrast, reader.GetByte());
directory.Set(TagBrightness, reader.GetByte());
directory.Set(TagSaturation, reader.GetByte());
directory.Set(TagHueAdjustment, reader.GetByte());
directory.Set(TagFilterEffect, reader.GetByte());
directory.Set(TagToningEffect, reader.GetByte());
directory.Set(TagToningSaturation, reader.GetByte());

Debug.Assert(reader.Position == ExpectedLength);

return directory;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// 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.

using static MetadataExtractor.Formats.Exif.Makernotes.NikonPictureControl2Directory;

namespace MetadataExtractor.Formats.Exif.Makernotes;

public sealed class NikonPictureControl2Descriptor : TagDescriptor<NikonPictureControl2Directory>
{
public NikonPictureControl2Descriptor(NikonPictureControl2Directory directory)
: base(directory)
{
}

public override string? GetDescription(int tagType)
{
return tagType switch
{
TagPictureControlAdjust => GetPictureControlAdjustDescription(),
TagFilterEffect => GetFilterEffectDescription(),
TagToningEffect => GetToningEffectDescription(),
_ => base.GetDescription(tagType)
};
}

public string? GetPictureControlAdjustDescription()
{
return GetIndexedDescription(
TagPictureControlAdjust,
"Default Settings",
"Quick Adjust",
"Full Control");
}

public string? GetFilterEffectDescription()
{
if (!Directory.TryGetByte(TagFilterEffect, out byte value))
return null;

return value switch
{
0x80 => "Off",
0x81 => "Yellow",
0x82 => "Orange",
0x83 => "Red",
0x84 => "Green",
0xFF => "N/A",
_ => base.GetDescription(TagFilterEffect)
};
}

public string? GetToningEffectDescription()
{
if (!Directory.TryGetByte(TagToningEffect, out byte value))
return null;

return value switch
{
0x80 => "B&W",
0x81 => "Sepia",
0x82 => "Cyanotype",
0x83 => "Red",
0x84 => "Yellow",
0x85 => "Green",
0x86 => "Blue-green",
0x87 => "Blue",
0x88 => "Purple-blue",
0x89 => "Red-purple",
0xFF => "N/A",
_ => base.GetDescription(TagToningEffect)
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// 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.

namespace MetadataExtractor.Formats.Exif.Makernotes;

public sealed class NikonPictureControl2Directory : Directory
{
// Tag values are offsets into the underlying data.
// Data from https://exiftool.org/TagNames/Nikon.html#PictureControl

public const int TagPictureControlVersion = 0;
public const int TagPictureControlName = 4;
public const int TagPictureControlBase = 24;
public const int TagPictureControlAdjust = 48;
public const int TagPictureControlQuickAdjust = 49;
// skip 1
public const int TagSharpness = 51;
// skip 1
public const int TagClarity = 53;
// skip 1
public const int TagContrast = 55;
// skip 1
public const int TagBrightness = 57;
// skip 1
public const int TagSaturation = 59;
// skip 1
public const int TagHue = 61;
// skip 1
public const int TagFilterEffect = 63;
public const int TagToningEffect = 64;
public const int TagToningSaturation = 65;

private static readonly Dictionary<int, string> _tagNameMap = new()
{
{ TagPictureControlVersion, "Picture Control Version" },
{ TagPictureControlName, "Picture Control Name" },
{ TagPictureControlBase, "Picture Control Base" },
{ TagPictureControlAdjust, "Picture Control Adjust" },
{ TagPictureControlQuickAdjust, "Picture Control Quick Adjust" },
{ TagSharpness, "Sharpness" },
{ TagClarity, "Clarity" },
{ TagContrast, "Contrast" },
{ TagBrightness, "Brightness" },
{ TagSaturation, "Saturation" },
{ TagHue, "Hue" },
{ TagFilterEffect, "Filter Effect" },
{ TagToningEffect, "Toning Effect" },
{ TagToningSaturation, "Toning Saturation" },
};

public NikonPictureControl2Directory() : base(_tagNameMap)
{
SetDescriptor(new NikonPictureControl2Descriptor(this));
}

public override string Name => "Nikon PictureControl 2";

internal static NikonPictureControl2Directory FromBytes(byte[] bytes)
{
const int ExpectedLength = 68;

if (bytes.Length != ExpectedLength)
{
throw new ArgumentException($"Must have {ExpectedLength} bytes.");
}

SequentialByteArrayReader reader = new(bytes);

NikonPictureControl2Directory directory = new();

directory.Set(TagPictureControlVersion, reader.GetStringValue(4));
directory.Set(TagPictureControlName, reader.GetStringValue(20));
directory.Set(TagPictureControlBase, reader.GetStringValue(20));
reader.Skip(4);
directory.Set(TagPictureControlAdjust, reader.GetByte());
directory.Set(TagPictureControlQuickAdjust, reader.GetByte());
reader.Skip(1);
directory.Set(TagSharpness, reader.GetByte());
reader.Skip(1);
directory.Set(TagClarity, reader.GetByte());
reader.Skip(1);
directory.Set(TagContrast, reader.GetByte());
reader.Skip(1);
directory.Set(TagBrightness, reader.GetByte());
reader.Skip(1);
directory.Set(TagSaturation, reader.GetByte());
reader.Skip(1);
directory.Set(TagHue, reader.GetByte());
reader.Skip(1);
directory.Set(TagFilterEffect, reader.GetByte());
directory.Set(TagToningEffect, reader.GetByte());
directory.Set(TagToningSaturation, reader.GetByte());
reader.Skip(2);

Debug.Assert(reader.Position == ExpectedLength);

return directory;
}
}
Loading