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.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
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 (TryGetIptc(frame.ExifProfile.Values, out var iptcBytes))
if (TryGetIptc(frame.ExifProfile.Values, out byte[] 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.
if (iptc.DataType == ExifDataType.Long)
{
var iptcValues = (uint[])iptc.GetValue();
uint[] iptcValues = (uint[])iptc.GetValue();
iptcBytes = new byte[iptcValues.Length * 4];
Buffer.BlockCopy(iptcValues, 0, iptcBytes, 0, iptcValues.Length * 4);
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(height);
this.collector.Add(software);
this.ProcessResolution(image.Metadata, frameMetadata);
this.ProcessProfiles(image.Metadata, frameMetadata);
this.ProcessMetadata(frameMetadata);
if (!this.collector.Entries.Exists(t => t.Tag == ExifTag.Software))
{
this.collector.Add(software);
}
}
private static bool IsPureMetadata(ExifTag tag)
@ -174,9 +177,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff
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
{

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

@ -24,12 +24,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// <summary>
/// ExifTags
/// </summary>
ExifTags = 4,
ExifTags = 2,
/// <summary>
/// GPSTags
/// </summary>
GpsTags = 8,
GpsTags = 4,
/// <summary>
/// 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));
[Theory]
[InlineData(TestImages.Tiff.RgbUncompressed, 24, 256, 256, 300, 300, PixelResolutionUnit.PixelsPerInch)]
[InlineData(TestImages.Tiff.SmallRgbDeflate, 24, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)]
[InlineData(TestImages.Tiff.Calliphora_GrayscaleUncompressed, 8, 804, 1198, 96, 96, PixelResolutionUnit.PixelsPerInch)]
[InlineData(RgbUncompressed, 24, 256, 256, 300, 300, PixelResolutionUnit.PixelsPerInch)]
[InlineData(SmallRgbDeflate, 24, 32, 32, 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)
{
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.VerticalResolution, frameMetaOut.VerticalResolution);
Assert.Equal("ImageSharp", frameMetaOut.ExifProfile.GetValue(ExifTag.Software).Value);
if (preserveMetadata)
{
Assert.Equal(tiffMeta.XmpProfile, tiffMetaOut.XmpProfile);
@ -311,6 +309,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
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.ImageDescription)?.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.Linq;
using System.Text;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
@ -28,7 +28,12 @@ namespace SixLabors.ImageSharp.Tests
/// <summary>
/// Writes a png file.
/// </summary>
Png
Png,
/// <summary>
/// Writes a tiff file.
/// </summary>
Tiff,
}
private static readonly Dictionary<ExifTag, object> TestProfileValues = new Dictionary<ExifTag, object>
@ -69,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void ConstructorEmpty()
{
new ExifProfile((byte[])null);
new ExifProfile(null);
new ExifProfile(new byte[] { });
}
@ -92,6 +97,7 @@ namespace SixLabors.ImageSharp.Tests
[Theory]
[InlineData(TestImageWriteFormat.Jpeg)]
[InlineData(TestImageWriteFormat.Png)]
[InlineData(TestImageWriteFormat.Tiff)]
public void WriteFraction(TestImageWriteFormat imageFormat)
{
using (var memStream = new MemoryStream())
@ -135,6 +141,7 @@ namespace SixLabors.ImageSharp.Tests
[Theory]
[InlineData(TestImageWriteFormat.Jpeg)]
[InlineData(TestImageWriteFormat.Png)]
[InlineData(TestImageWriteFormat.Tiff)]
public void ReadWriteInfinity(TestImageWriteFormat imageFormat)
{
Image<Rgba32> image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image();
@ -161,9 +168,17 @@ namespace SixLabors.ImageSharp.Tests
}
[Theory]
[InlineData(TestImageWriteFormat.Jpeg)]
[InlineData(TestImageWriteFormat.Png)]
public void SetValue(TestImageWriteFormat imageFormat)
/* The original exif profile has 19 values, the written profile should be 3 less.
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 */
[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.Metadata.ExifProfile.SetValue(ExifTag.Software, "ImageSharp");
@ -206,18 +221,12 @@ namespace SixLabors.ImageSharp.Tests
// todo: duplicate tags
Assert.Equal(2, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932));
int profileCount = image.Metadata.ExifProfile.Values.Count;
image = WriteAndRead(image, imageFormat);
Assert.NotNull(image.Metadata.ExifProfile);
Assert.Equal(0, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932));
// Should be 3 less.
// 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);
Assert.Equal(expectedProfileValueCount, image.Metadata.ExifProfile.Values.Count);
software = image.Metadata.ExifProfile.GetValue(ExifTag.Software);
Assert.Equal("15", software.Value);
@ -233,20 +242,42 @@ namespace SixLabors.ImageSharp.Tests
latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude);
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;
// Act
image = WriteAndRead(image, imageFormat);
// Assert
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.True(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace));
Assert.False(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace));
Assert.Null(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace));
Assert.Equal(7, image.Metadata.ExifProfile.Values.Count);
Assert.Equal(profileCount - 1, image.Metadata.ExifProfile.Values.Count);
}
[Fact]
@ -382,6 +413,7 @@ namespace SixLabors.ImageSharp.Tests
[Theory]
[InlineData(TestImageWriteFormat.Jpeg)]
[InlineData(TestImageWriteFormat.Png)]
[InlineData(TestImageWriteFormat.Tiff)]
public void WritingImagePreservesExifProfile(TestImageWriteFormat imageFormat)
{
// Arrange
@ -456,8 +488,10 @@ namespace SixLabors.ImageSharp.Tests
return WriteAndReadJpeg(image);
case TestImageWriteFormat.Png:
return WriteAndReadPng(image);
case TestImageWriteFormat.Tiff:
return WriteAndReadTiff(image);
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)
{
Assert.NotNull(profile);

Loading…
Cancel
Save