Browse Source

- no longer exposing raw exif data byte's, using ToByteArray to write the ExifChunk

- changed the ExifWriter GetData, to support the special case for PNG's not inlcluding the 'Exif' Code
(#611)
af/merge-core
popow 8 years ago
parent
commit
08797b1031
  1. 4
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  2. 11
      src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs
  3. 1
      src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs
  4. 76
      src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs
  5. 68
      tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs

4
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -398,7 +398,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary> /// <summary>
/// Calculates the correct number of bytes per pixel for the given color type. /// Calculates the correct number of bytes per pixel for the given color type.
/// </summary> /// </summary>
/// <returns>The <see cref="int"/></returns> /// <returns>Bytes per pixel</returns>
private int CalculateBytesPerPixel() private int CalculateBytesPerPixel()
{ {
switch (this.pngColorType) switch (this.pngColorType)
@ -535,7 +535,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{ {
if (image.MetaData.ExifProfile?.Values.Count > 0) if (image.MetaData.ExifProfile?.Values.Count > 0)
{ {
this.WriteChunk(stream, PngChunkType.Exif, image.MetaData.ExifProfile.RawData); this.WriteChunk(stream, PngChunkType.Exif, image.MetaData.ExifProfile.ToByteArray(includeExifIdCode: false));
} }
} }

11
src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs

@ -94,11 +94,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// </summary> /// </summary>
public ExifParts Parts { get; set; } public ExifParts Parts { get; set; }
/// <summary>
/// Gets the byte data array containing the exif data.
/// </summary>
public byte[] RawData => this.data;
/// <summary> /// <summary>
/// Gets the tags that where found but contained an invalid value. /// Gets the tags that where found but contained an invalid value.
/// </summary> /// </summary>
@ -237,8 +232,10 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <summary> /// <summary>
/// Converts this instance to a byte array. /// Converts this instance to a byte array.
/// </summary> /// </summary>
/// <param name="includeExifIdCode">Indicates, if the Exif ID code should be included.
/// This Exif ID code should not be included in case of PNG's. Defaults to true.</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
public byte[] ToByteArray() public byte[] ToByteArray(bool includeExifIdCode = true)
{ {
if (this.values == null) if (this.values == null)
{ {
@ -251,7 +248,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
} }
var writer = new ExifWriter(this.values, this.Parts); var writer = new ExifWriter(this.values, this.Parts);
return writer.GetData(); return writer.GetData(includeExifIdCode);
} }
/// <summary> /// <summary>

1
src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs

@ -78,6 +78,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
if (this.ReadString(4) == "Exif") if (this.ReadString(4) == "Exif")
{ {
// two zeros are expected to follow the Exif Id code
if (this.ReadUInt16() != 0) if (this.ReadUInt16() != 0)
{ {
return values; return values;

76
src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs

@ -14,11 +14,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// </summary> /// </summary>
internal sealed class ExifWriter internal sealed class ExifWriter
{ {
/// <summary>
/// The start index.
/// </summary>
private const int StartIndex = 6;
/// <summary> /// <summary>
/// Which parts will be written. /// Which parts will be written.
/// </summary> /// </summary>
@ -46,11 +41,14 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <summary> /// <summary>
/// Returns the EXIF data. /// Returns the EXIF data.
/// </summary> /// </summary>
/// <param name="includeExifIdCode">Indicates, if the Exif ID code should be included.
/// This Exif ID code should not be included in case of PNG's. Defaults to true.</param>
/// <returns> /// <returns>
/// The <see cref="T:byte[]"/>. /// The <see cref="T:byte[]"/>.
/// </returns> /// </returns>
public byte[] GetData() public byte[] GetData(bool includeExifIdCode = true)
{ {
uint startIndex = 6;
uint length; uint length;
int exifIndex = -1; int exifIndex = -1;
int gpsIndex = -1; int gpsIndex = -1;
@ -86,23 +84,51 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return null; return null;
} }
length += 10 + 4 + 2; if (includeExifIdCode)
{
// Exif Code (6 bytes) + byte order marker (4 bytes)
length += 10;
}
else
{
// special case for PNG eXIf Chunk:
// two bytes for the byte Order marker 'II', followed by the number 42 (0x2A) and a 0, making 4 bytes total
length += 4;
// if the Exif Code ("Exif00") is not included, the start index is 0 instead of 6
startIndex = 0;
}
length += 4 + 2;
byte[] result = new byte[length]; byte[] result = new byte[length];
result[0] = (byte)'E'; int i = 0;
result[1] = (byte)'x'; if (includeExifIdCode)
result[2] = (byte)'i'; {
result[3] = (byte)'f'; result[0] = (byte)'E';
result[4] = 0x00; result[1] = (byte)'x';
result[5] = 0x00; result[2] = (byte)'i';
result[6] = (byte)'I'; result[3] = (byte)'f';
result[7] = (byte)'I'; result[4] = 0x00;
result[8] = 0x2A; result[5] = 0x00;
result[9] = 0x00; result[6] = (byte)'I';
result[7] = (byte)'I';
int i = 10; result[8] = 0x2A;
uint ifdOffset = ((uint)i - StartIndex) + 4; result[9] = 0x00;
i = 10;
}
else
{
// the byte order marker followed by the number 42 and a 0
result[0] = (byte)'I';
result[1] = (byte)'I';
result[2] = 0x2A;
result[3] = 0x00;
i = 4;
}
uint ifdOffset = ((uint)i - startIndex) + 4;
uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength; uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength;
if (exifLength > 0) if (exifLength > 0)
@ -118,18 +144,18 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
i = WriteUInt32(ifdOffset, result, i); i = WriteUInt32(ifdOffset, result, i);
i = this.WriteHeaders(this.ifdIndexes, result, i); i = this.WriteHeaders(this.ifdIndexes, result, i);
i = WriteUInt32(thumbnailOffset, result, i); i = WriteUInt32(thumbnailOffset, result, i);
i = this.WriteData(this.ifdIndexes, result, i); i = this.WriteData(startIndex, this.ifdIndexes, result, i);
if (exifLength > 0) if (exifLength > 0)
{ {
i = this.WriteHeaders(this.exifIndexes, result, i); i = this.WriteHeaders(this.exifIndexes, result, i);
i = this.WriteData(this.exifIndexes, result, i); i = this.WriteData(startIndex, this.exifIndexes, result, i);
} }
if (gpsLength > 0) if (gpsLength > 0)
{ {
i = this.WriteHeaders(this.gpsIndexes, result, i); i = this.WriteHeaders(this.gpsIndexes, result, i);
i = this.WriteData(this.gpsIndexes, result, i); i = this.WriteData(startIndex, this.gpsIndexes, result, i);
} }
WriteUInt16((ushort)0, result, i); WriteUInt16((ushort)0, result, i);
@ -266,7 +292,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return newOffset; return newOffset;
} }
private int WriteData(List<int> indexes, byte[] destination, int offset) private int WriteData(uint startIndex, List<int> indexes, byte[] destination, int offset)
{ {
if (this.dataOffsets.Count == 0) if (this.dataOffsets.Count == 0)
{ {
@ -281,7 +307,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
ExifValue value = this.values[index]; ExifValue value = this.values[index];
if (value.Length > 4) if (value.Length > 4)
{ {
WriteUInt32((uint)(newOffset - StartIndex), destination, this.dataOffsets[i++]); WriteUInt32((uint)(newOffset - startIndex), destination, this.dataOffsets[i++]);
newOffset = this.WriteValue(value, destination, newOffset); newOffset = this.WriteValue(value, destination, newOffset);
} }
} }

68
tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -17,6 +18,16 @@ namespace SixLabors.ImageSharp.Tests
{ {
public class ExifProfileTests public class ExifProfileTests
{ {
private static readonly Dictionary<ExifTag, object> TestProfileValues = new Dictionary<ExifTag, object>()
{
{ ExifTag.Software, "Software" },
{ ExifTag.Model, "Model" },
{ ExifTag.Copyright, "Copyright" },
{ ExifTag.Orientation, (ushort)5 },
{ ExifTag.ShutterSpeedValue, new SignedRational(75.55) },
{ ExifTag.ExposureTime, new Rational(1.0 / 1600.0) },
};
[Fact] [Fact]
public void Constructor() public void Constructor()
{ {
@ -27,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests
image.MetaData.ExifProfile = new ExifProfile(); image.MetaData.ExifProfile = new ExifProfile();
image.MetaData.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra"); image.MetaData.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra");
image = WriteAndRead(image); image = WriteAndReadJpeg(image);
Assert.NotNull(image.MetaData.ExifProfile); Assert.NotNull(image.MetaData.ExifProfile);
Assert.Equal(1, image.MetaData.ExifProfile.Values.Count()); Assert.Equal(1, image.MetaData.ExifProfile.Values.Count());
@ -50,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests
ExifProfile profile = GetExifProfile(); ExifProfile profile = GetExifProfile();
ExifProfile clone = new ExifProfile(profile); var clone = new ExifProfile(profile);
TestProfile(clone); TestProfile(clone);
profile.SetValue(ExifTag.ColorSpace, (ushort)2); profile.SetValue(ExifTag.ColorSpace, (ushort)2);
@ -62,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void WriteFraction() public void WriteFraction()
{ {
using (MemoryStream memStream = new MemoryStream()) using (var memStream = new MemoryStream())
{ {
double exposureTime = 1.0 / 1600; double exposureTime = 1.0 / 1600;
@ -70,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests
profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime)); profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime));
Image<Rgba32> image = new Image<Rgba32>(1, 1); var image = new Image<Rgba32>(1, 1);
image.MetaData.ExifProfile = profile; image.MetaData.ExifProfile = profile;
image.SaveAsJpeg(memStream); image.SaveAsJpeg(memStream);
@ -110,21 +121,21 @@ namespace SixLabors.ImageSharp.Tests
Image<Rgba32> image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); Image<Rgba32> image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage();
image.MetaData.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity)); image.MetaData.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity));
image = WriteAndRead(image); image = WriteAndReadJpeg(image);
ExifValue value = image.MetaData.ExifProfile.GetValue(ExifTag.ExposureBiasValue); ExifValue value = image.MetaData.ExifProfile.GetValue(ExifTag.ExposureBiasValue);
Assert.NotNull(value); Assert.NotNull(value);
Assert.Equal(new SignedRational(double.PositiveInfinity), value.Value); Assert.Equal(new SignedRational(double.PositiveInfinity), value.Value);
image.MetaData.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity)); image.MetaData.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity));
image = WriteAndRead(image); image = WriteAndReadJpeg(image);
value = image.MetaData.ExifProfile.GetValue(ExifTag.ExposureBiasValue); value = image.MetaData.ExifProfile.GetValue(ExifTag.ExposureBiasValue);
Assert.NotNull(value); Assert.NotNull(value);
Assert.Equal(new SignedRational(double.NegativeInfinity), value.Value); Assert.Equal(new SignedRational(double.NegativeInfinity), value.Value);
image.MetaData.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity)); image.MetaData.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity));
image = WriteAndRead(image); image = WriteAndReadJpeg(image);
value = image.MetaData.ExifProfile.GetValue(ExifTag.FlashEnergy); value = image.MetaData.ExifProfile.GetValue(ExifTag.FlashEnergy);
Assert.NotNull(value); Assert.NotNull(value);
Assert.Equal(new Rational(double.PositiveInfinity), value.Value); Assert.Equal(new Rational(double.PositiveInfinity), value.Value);
@ -133,7 +144,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void SetValue() public void SetValue()
{ {
Rational[] latitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) }; var latitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) };
Image<Rgba32> image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); Image<Rgba32> image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage();
image.MetaData.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); image.MetaData.ExifProfile.SetValue(ExifTag.Software, "ImageSharp");
@ -171,7 +182,7 @@ namespace SixLabors.ImageSharp.Tests
value = image.MetaData.ExifProfile.GetValue(ExifTag.GPSLatitude); value = image.MetaData.ExifProfile.GetValue(ExifTag.GPSLatitude);
TestValue(value, latitude); TestValue(value, latitude);
image = WriteAndRead(image); image = WriteAndReadJpeg(image);
Assert.NotNull(image.MetaData.ExifProfile); Assert.NotNull(image.MetaData.ExifProfile);
Assert.Equal(17, image.MetaData.ExifProfile.Values.Count()); Assert.Equal(17, image.MetaData.ExifProfile.Values.Count());
@ -193,7 +204,7 @@ namespace SixLabors.ImageSharp.Tests
image.MetaData.ExifProfile.Parts = ExifParts.ExifTags; image.MetaData.ExifProfile.Parts = ExifParts.ExifTags;
image = WriteAndRead(image); image = WriteAndReadJpeg(image);
Assert.NotNull(image.MetaData.ExifProfile); Assert.NotNull(image.MetaData.ExifProfile);
Assert.Equal(8, image.MetaData.ExifProfile.Values.Count()); Assert.Equal(8, image.MetaData.ExifProfile.Values.Count());
@ -312,17 +323,34 @@ namespace SixLabors.ImageSharp.Tests
public void TestWritingPngPreservesExifProfile() public void TestWritingPngPreservesExifProfile()
{ {
// arrange // arrange
Image<Rgba32> image = TestFile.Create(TestImages.Png.Bike).CreateImage(); var image = new Image<Rgba32>(1, 1);
ExifProfile expected = GetExifProfile(); ExifProfile expected = CreateExifProfile();
image.MetaData.ExifProfile = expected; image.MetaData.ExifProfile = expected;
// act // act
Image<Rgba32> reloadedImage = WritePngAndRead(image); Image<Rgba32> reloadedImage = WriteAndReadPng(image);
// assert // assert
ExifProfile actual = reloadedImage.MetaData.ExifProfile; ExifProfile actual = reloadedImage.MetaData.ExifProfile;
Assert.NotNull(actual); Assert.NotNull(actual);
TestProfile(actual); foreach(KeyValuePair<ExifTag, object> expectedProfileValue in TestProfileValues)
{
ExifValue actualProfileValue = actual.GetValue(expectedProfileValue.Key);
Assert.NotNull(actualProfileValue);
Assert.Equal(expectedProfileValue.Value, actualProfileValue.Value);
}
}
private static ExifProfile CreateExifProfile()
{
var profile = new ExifProfile();
foreach(KeyValuePair<ExifTag, object> exifProfileValue in TestProfileValues)
{
profile.SetValue(exifProfileValue.Key, exifProfileValue.Value);
}
return profile;
} }
private static ExifProfile GetExifProfile() private static ExifProfile GetExifProfile()
@ -335,7 +363,7 @@ namespace SixLabors.ImageSharp.Tests
return profile; return profile;
} }
private static Image<Rgba32> WriteAndRead(Image<Rgba32> image) private static Image<Rgba32> WriteAndReadJpeg(Image<Rgba32> image)
{ {
using (var memStream = new MemoryStream()) using (var memStream = new MemoryStream())
{ {
@ -347,7 +375,7 @@ namespace SixLabors.ImageSharp.Tests
} }
} }
private static Image<Rgba32> WritePngAndRead(Image<Rgba32> image) private static Image<Rgba32> WriteAndReadPng(Image<Rgba32> image)
{ {
using (var memStream = new MemoryStream()) using (var memStream = new MemoryStream())
{ {
@ -370,13 +398,19 @@ namespace SixLabors.ImageSharp.Tests
Assert.NotNull(value.Value); Assert.NotNull(value.Value);
if (value.Tag == ExifTag.Software) if (value.Tag == ExifTag.Software)
{
Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.ToString()); Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.ToString());
}
if (value.Tag == ExifTag.XResolution) if (value.Tag == ExifTag.XResolution)
{
Assert.Equal(new Rational(300.0), value.Value); Assert.Equal(new Rational(300.0), value.Value);
}
if (value.Tag == ExifTag.PixelXDimension) if (value.Tag == ExifTag.PixelXDimension)
{
Assert.Equal(2338U, value.Value); Assert.Equal(2338U, value.Value);
}
} }
} }

Loading…
Cancel
Save