Browse Source

Read and write Exif Profile

pull/1553/head
Brian Popow 5 years ago
parent
commit
fa68e1b843
  1. 10
      src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
  2. 22
      src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
  3. 4
      src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs
  4. 6
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  5. 3
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  6. 80
      tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs

10
src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs

@ -4,7 +4,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc;
@ -57,9 +56,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
} }
} }
if (coreMetadata.ExifProfile == null)
{
coreMetadata.ExifProfile = frame?.ExifProfile.DeepClone();
}
if (coreMetadata.IptcProfile == null) if (coreMetadata.IptcProfile == null)
{ {
if (TryGetIptc(frame.ExifProfile.Values, out var iptcBytes)) if (TryGetIptc(frame.ExifProfile.Values, out byte[] iptcBytes))
{ {
coreMetadata.IptcProfile = new IptcProfile(iptcBytes); coreMetadata.IptcProfile = new IptcProfile(iptcBytes);
} }
@ -95,7 +99,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
// Some Encoders write the data type of IPTC as long. // Some Encoders write the data type of IPTC as long.
if (iptc.DataType == ExifDataType.Long) if (iptc.DataType == ExifDataType.Long)
{ {
var iptcValues = (uint[])iptc.GetValue(); uint[] iptcValues = (uint[])iptc.GetValue();
iptcBytes = new byte[iptcValues.Length * 4]; iptcBytes = new byte[iptcValues.Length * 4];
Buffer.BlockCopy(iptcValues, 0, iptcBytes, 0, iptcValues.Length * 4); Buffer.BlockCopy(iptcValues, 0, iptcBytes, 0, iptcValues.Length * 4);
if (iptcBytes[0] == 0x1c) if (iptcBytes[0] == 0x1c)

22
src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs

@ -66,12 +66,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.collector.Add(width); this.collector.Add(width);
this.collector.Add(height); this.collector.Add(height);
this.collector.Add(software);
this.ProcessResolution(image.Metadata, frameMetadata); this.ProcessResolution(image.Metadata, frameMetadata);
this.ProcessProfiles(image.Metadata, frameMetadata); this.ProcessProfiles(image.Metadata, frameMetadata);
this.ProcessMetadata(frameMetadata); this.ProcessMetadata(frameMetadata);
if (!this.collector.Entries.Exists(t => t.Tag == ExifTag.Software))
{
this.collector.Add(software);
}
} }
private static bool IsPureMetadata(ExifTag tag) private static bool IsPureMetadata(ExifTag tag)
@ -174,9 +177,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff
private void ProcessProfiles(ImageMetadata imageMetadata, TiffFrameMetadata tiffFrameMetadata) private void ProcessProfiles(ImageMetadata imageMetadata, TiffFrameMetadata tiffFrameMetadata)
{ {
if (imageMetadata.ExifProfile != null) if (imageMetadata.ExifProfile != null && imageMetadata.ExifProfile.Parts != ExifParts.None)
{ {
// todo: implement processing exif profile imageMetadata.SyncProfiles();
foreach (IExifValue entry in imageMetadata.ExifProfile.Values)
{
if (!this.collector.Entries.Exists(t => t.Tag == entry.Tag) && entry.GetValue() != null)
{
ExifParts entryPart = ExifTags.GetPart(entry.Tag);
if (entryPart != ExifParts.None && imageMetadata.ExifProfile.Parts.HasFlag(entryPart))
{
this.collector.AddOrReplace(entry.DeepClone());
}
}
}
} }
else else
{ {

4
src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs

@ -24,12 +24,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// <summary> /// <summary>
/// ExifTags /// ExifTags
/// </summary> /// </summary>
ExifTags = 4, ExifTags = 2,
/// <summary> /// <summary>
/// GPSTags /// GPSTags
/// </summary> /// </summary>
GpsTags = 8, GpsTags = 4,
/// <summary> /// <summary>
/// All /// All

6
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -42,9 +42,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
where TPixel : unmanaged, IPixel<TPixel> => Assert.Throws<NotSupportedException>(() => provider.GetImage(TiffDecoder)); where TPixel : unmanaged, IPixel<TPixel> => Assert.Throws<NotSupportedException>(() => provider.GetImage(TiffDecoder));
[Theory] [Theory]
[InlineData(TestImages.Tiff.RgbUncompressed, 24, 256, 256, 300, 300, PixelResolutionUnit.PixelsPerInch)] [InlineData(RgbUncompressed, 24, 256, 256, 300, 300, PixelResolutionUnit.PixelsPerInch)]
[InlineData(TestImages.Tiff.SmallRgbDeflate, 24, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(SmallRgbDeflate, 24, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)]
[InlineData(TestImages.Tiff.Calliphora_GrayscaleUncompressed, 8, 804, 1198, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(Calliphora_GrayscaleUncompressed, 8, 804, 1198, 96, 96, PixelResolutionUnit.PixelsPerInch)]
public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);

3
tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs

@ -292,8 +292,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.Equal(frameMeta.HorizontalResolution, frameMetaOut.HorizontalResolution); Assert.Equal(frameMeta.HorizontalResolution, frameMetaOut.HorizontalResolution);
Assert.Equal(frameMeta.VerticalResolution, frameMetaOut.VerticalResolution); Assert.Equal(frameMeta.VerticalResolution, frameMetaOut.VerticalResolution);
Assert.Equal("ImageSharp", frameMetaOut.ExifProfile.GetValue(ExifTag.Software).Value);
if (preserveMetadata) if (preserveMetadata)
{ {
Assert.Equal(tiffMeta.XmpProfile, tiffMetaOut.XmpProfile); Assert.Equal(tiffMeta.XmpProfile, tiffMetaOut.XmpProfile);
@ -311,6 +309,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{ {
Assert.Null(tiffMetaOut.XmpProfile); Assert.Null(tiffMetaOut.XmpProfile);
Assert.Equal("ImageSharp", frameMetaOut.ExifProfile.GetValue(ExifTag.Software).Value);
Assert.Null(frameMeta.ExifProfile.GetValue(ExifTag.Software)?.Value); Assert.Null(frameMeta.ExifProfile.GetValue(ExifTag.Software)?.Value);
Assert.Null(frameMeta.ExifProfile.GetValue(ExifTag.ImageDescription)?.Value); Assert.Null(frameMeta.ExifProfile.GetValue(ExifTag.ImageDescription)?.Value);
Assert.Null(frameMeta.ExifProfile.GetValue(ExifTag.Make)?.Value); Assert.Null(frameMeta.ExifProfile.GetValue(ExifTag.Make)?.Value);

80
tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs

@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -28,7 +28,12 @@ namespace SixLabors.ImageSharp.Tests
/// <summary> /// <summary>
/// Writes a png file. /// Writes a png file.
/// </summary> /// </summary>
Png Png,
/// <summary>
/// Writes a tiff file.
/// </summary>
Tiff,
} }
private static readonly Dictionary<ExifTag, object> TestProfileValues = new Dictionary<ExifTag, object> private static readonly Dictionary<ExifTag, object> TestProfileValues = new Dictionary<ExifTag, object>
@ -69,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void ConstructorEmpty() public void ConstructorEmpty()
{ {
new ExifProfile((byte[])null); new ExifProfile(null);
new ExifProfile(new byte[] { }); new ExifProfile(new byte[] { });
} }
@ -92,6 +97,7 @@ namespace SixLabors.ImageSharp.Tests
[Theory] [Theory]
[InlineData(TestImageWriteFormat.Jpeg)] [InlineData(TestImageWriteFormat.Jpeg)]
[InlineData(TestImageWriteFormat.Png)] [InlineData(TestImageWriteFormat.Png)]
[InlineData(TestImageWriteFormat.Tiff)]
public void WriteFraction(TestImageWriteFormat imageFormat) public void WriteFraction(TestImageWriteFormat imageFormat)
{ {
using (var memStream = new MemoryStream()) using (var memStream = new MemoryStream())
@ -135,6 +141,7 @@ namespace SixLabors.ImageSharp.Tests
[Theory] [Theory]
[InlineData(TestImageWriteFormat.Jpeg)] [InlineData(TestImageWriteFormat.Jpeg)]
[InlineData(TestImageWriteFormat.Png)] [InlineData(TestImageWriteFormat.Png)]
[InlineData(TestImageWriteFormat.Tiff)]
public void ReadWriteInfinity(TestImageWriteFormat imageFormat) public void ReadWriteInfinity(TestImageWriteFormat imageFormat)
{ {
Image<Rgba32> image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); Image<Rgba32> image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image();
@ -161,9 +168,17 @@ namespace SixLabors.ImageSharp.Tests
} }
[Theory] [Theory]
[InlineData(TestImageWriteFormat.Jpeg)] /* The original exif profile has 19 values, the written profile should be 3 less.
[InlineData(TestImageWriteFormat.Png)] 1 x due to setting of null "ReferenceBlackWhite" value.
public void SetValue(TestImageWriteFormat imageFormat) 2 x due to use of non-standard padding tag 0xEA1C listed in EXIF Tool. We can read those values but adhere
strictly to the 2.3.1 specification when writing. (TODO: Support 2.3.2)
https://exiftool.org/TagNames/EXIF.html */
[InlineData(TestImageWriteFormat.Jpeg, 16)]
[InlineData(TestImageWriteFormat.Png, 16)]
/* Note: The tiff format has 24 expected profile values, because some tiff specific exif
values will be written in addition to the original profile. */
[InlineData(TestImageWriteFormat.Tiff, 24)]
public void SetValue(TestImageWriteFormat imageFormat, int expectedProfileValueCount)
{ {
Image<Rgba32> image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); Image<Rgba32> image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image();
image.Metadata.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); image.Metadata.ExifProfile.SetValue(ExifTag.Software, "ImageSharp");
@ -206,18 +221,12 @@ namespace SixLabors.ImageSharp.Tests
// todo: duplicate tags // todo: duplicate tags
Assert.Equal(2, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932)); Assert.Equal(2, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932));
int profileCount = image.Metadata.ExifProfile.Values.Count;
image = WriteAndRead(image, imageFormat); image = WriteAndRead(image, imageFormat);
Assert.NotNull(image.Metadata.ExifProfile); Assert.NotNull(image.Metadata.ExifProfile);
Assert.Equal(0, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932)); Assert.Equal(0, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932));
// Should be 3 less. Assert.Equal(expectedProfileValueCount, image.Metadata.ExifProfile.Values.Count);
// 1 x due to setting of null "ReferenceBlackWhite" value.
// 2 x due to use of non-standard padding tag 0xEA1C listed in EXIF Tool. We can read those values but adhere
// strictly to the 2.3.1 specification when writing. (TODO: Support 2.3.2)
// https://exiftool.org/TagNames/EXIF.html
Assert.Equal(profileCount - 3, image.Metadata.ExifProfile.Values.Count);
software = image.Metadata.ExifProfile.GetValue(ExifTag.Software); software = image.Metadata.ExifProfile.GetValue(ExifTag.Software);
Assert.Equal("15", software.Value); Assert.Equal("15", software.Value);
@ -233,20 +242,42 @@ namespace SixLabors.ImageSharp.Tests
latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude);
Assert.Equal(expectedLatitude, latitude.Value); Assert.Equal(expectedLatitude, latitude.Value);
}
[Theory]
[InlineData(TestImageWriteFormat.Jpeg)]
[InlineData(TestImageWriteFormat.Png)]
public void WriteOnlyExifTags_Works(TestImageWriteFormat imageFormat)
{
// Arrange
Image<Rgba32> image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image();
image.Metadata.ExifProfile.Parts = ExifParts.ExifTags; image.Metadata.ExifProfile.Parts = ExifParts.ExifTags;
// Act
image = WriteAndRead(image, imageFormat); image = WriteAndRead(image, imageFormat);
// Assert
Assert.NotNull(image.Metadata.ExifProfile); Assert.NotNull(image.Metadata.ExifProfile);
Assert.Equal(8, image.Metadata.ExifProfile.Values.Count); Assert.Equal(7, image.Metadata.ExifProfile.Values.Count);
foreach (IExifValue exifProfileValue in image.Metadata.ExifProfile.Values)
{
Assert.True(ExifTags.GetPart(exifProfileValue.Tag) == ExifParts.ExifTags);
}
}
[Fact]
public void RemoveEntry_Works()
{
// Arrange
Image<Rgba32> image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image();
int profileCount = image.Metadata.ExifProfile.Values.Count;
// Assert
Assert.NotNull(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace)); Assert.NotNull(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace));
Assert.True(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace)); Assert.True(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace));
Assert.False(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace)); Assert.False(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace));
Assert.Null(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace)); Assert.Null(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace));
Assert.Equal(profileCount - 1, image.Metadata.ExifProfile.Values.Count);
Assert.Equal(7, image.Metadata.ExifProfile.Values.Count);
} }
[Fact] [Fact]
@ -382,6 +413,7 @@ namespace SixLabors.ImageSharp.Tests
[Theory] [Theory]
[InlineData(TestImageWriteFormat.Jpeg)] [InlineData(TestImageWriteFormat.Jpeg)]
[InlineData(TestImageWriteFormat.Png)] [InlineData(TestImageWriteFormat.Png)]
[InlineData(TestImageWriteFormat.Tiff)]
public void WritingImagePreservesExifProfile(TestImageWriteFormat imageFormat) public void WritingImagePreservesExifProfile(TestImageWriteFormat imageFormat)
{ {
// Arrange // Arrange
@ -456,8 +488,10 @@ namespace SixLabors.ImageSharp.Tests
return WriteAndReadJpeg(image); return WriteAndReadJpeg(image);
case TestImageWriteFormat.Png: case TestImageWriteFormat.Png:
return WriteAndReadPng(image); return WriteAndReadPng(image);
case TestImageWriteFormat.Tiff:
return WriteAndReadTiff(image);
default: default:
throw new ArgumentException("Unexpected test image format, only Jpeg and Png are allowed"); throw new ArgumentException("Unexpected test image format, only Jpeg, Png and Tiff are allowed");
} }
} }
@ -485,6 +519,18 @@ namespace SixLabors.ImageSharp.Tests
} }
} }
private static Image<Rgba32> WriteAndReadTiff(Image<Rgba32> image)
{
using (var memStream = new MemoryStream())
{
image.SaveAsTiff(memStream, new TiffEncoder());
image.Dispose();
memStream.Position = 0;
return Image.Load<Rgba32>(memStream, new TiffDecoder());
}
}
private static void TestProfile(ExifProfile profile) private static void TestProfile(ExifProfile profile)
{ {
Assert.NotNull(profile); Assert.NotNull(profile);

Loading…
Cancel
Save