Browse Source

Make ExifValue immutable

pull/506/head
Jason Nelson 8 years ago
parent
commit
52b16a32be
  1. 9
      src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs
  2. 163
      src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs
  3. 32
      src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs
  4. 10
      tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs

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

@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <summary>
/// Gets the values of this EXIF profile.
/// </summary>
public IEnumerable<ExifValue> Values
public IList<ExifValue> Values
{
get
{
@ -193,16 +193,17 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <param name="value">The value.</param>
public void SetValue(ExifTag tag, object value)
{
foreach (ExifValue exifValue in this.Values)
for (int i = 0; i < this.Values.Count; i++)
{
if (exifValue.Tag == tag)
if (this.Values[i].Tag == tag)
{
exifValue.Value = value;
this.Values[i] = this.Values[i].WithValue(value);
return;
}
}
var newExifValue = ExifValue.Create(tag, value);
this.values.Add(newExifValue);
}

163
src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs

@ -13,11 +13,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// </summary>
public sealed class ExifValue : IEquatable<ExifValue>
{
/// <summary>
/// The exif value.
/// </summary>
private object exifValue;
/// <summary>
/// Initializes a new instance of the <see cref="ExifValue"/> class
/// by making a copy from another exif value.
@ -31,33 +26,16 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
this.DataType = other.DataType;
this.IsArray = other.IsArray;
this.Tag = other.Tag;
if (!other.IsArray)
{
this.exifValue = other.exifValue;
this.Value = other.Value;
}
else
{
var array = (Array)other.exifValue;
this.exifValue = array.Clone();
}
}
/// <summary>
/// Initializes a new instance of the <see cref="ExifValue"/> class.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="dataType">The data type.</param>
/// <param name="isArray">Whether the value is an array.</param>
internal ExifValue(ExifTag tag, ExifDataType dataType, bool isArray)
{
this.Tag = tag;
this.DataType = dataType;
this.IsArray = isArray;
if (dataType == ExifDataType.Ascii)
{
this.IsArray = false;
var array = (Array)other.Value;
this.Value = array.Clone();
}
}
@ -69,9 +47,13 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <param name="value">The value.</param>
/// <param name="isArray">Whether the value is an array.</param>
internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray)
: this(tag, dataType, isArray)
{
this.exifValue = value;
this.Tag = tag;
this.DataType = dataType;
this.IsArray = isArray && dataType != ExifDataType.Ascii;
this.Value = value;
// this.CheckValue(value);
}
/// <summary>
@ -90,17 +72,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
public ExifTag Tag { get; }
/// <summary>
/// Gets or sets the value.
/// Gets the value.
/// </summary>
public object Value
{
get => this.exifValue;
set
{
this.CheckValue(value);
this.exifValue = value;
}
}
public object Value { get; }
/// <summary>
/// Gets a value indicating whether the EXIF value has a value.
@ -109,14 +83,14 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{
get
{
if (this.exifValue == null)
if (this.Value == null)
{
return false;
}
if (this.DataType == ExifDataType.Ascii)
{
return ((string)this.exifValue).Length > 0;
return ((string)this.Value).Length > 0;
}
return true;
@ -130,7 +104,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{
get
{
if (this.exifValue == null)
if (this.Value == null)
{
return 4;
}
@ -150,18 +124,30 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{
if (this.DataType == ExifDataType.Ascii)
{
return Encoding.UTF8.GetBytes((string)this.exifValue).Length;
return Encoding.UTF8.GetBytes((string)this.Value).Length;
}
if (this.IsArray)
{
return ((Array)this.exifValue).Length;
return ((Array)this.Value).Length;
}
return 1;
}
}
/// <summary>
/// Clones the current value, overwriting the value.
/// </summary>
/// <param name="value">The value to overwrite.</param>
/// <returns><see cref="ExifValue"/></returns>
public ExifValue WithValue(object value)
{
this.CheckValue(value);
return new ExifValue(this.Tag, this.DataType, value, this.IsArray);
}
/// <summary>
/// Compares two <see cref="ExifValue"/> objects for equality.
/// </summary>
@ -223,7 +209,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return
this.Tag == other.Tag &&
this.DataType == other.DataType &&
object.Equals(this.exifValue, other.exifValue);
object.Equals(this.Value, other.Value);
}
/// <inheritdoc/>
@ -235,23 +221,23 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <inheritdoc/>
public override string ToString()
{
if (this.exifValue == null)
if (this.Value == null)
{
return null;
}
if (this.DataType == ExifDataType.Ascii)
{
return (string)this.exifValue;
return (string)this.Value;
}
if (!this.IsArray)
{
return this.ToString(this.exifValue);
return this.ToString(this.Value);
}
var sb = new StringBuilder();
foreach (object value in (Array)this.exifValue)
foreach (object value in (Array)this.Value)
{
sb.Append(this.ToString(value));
sb.Append(' ');
@ -275,13 +261,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{
Guard.IsFalse(tag == ExifTag.Unknown, nameof(tag), "Invalid Tag");
ExifValue exifValue;
Type type = value?.GetType();
if (type != null && type.IsArray)
{
type = type.GetElementType();
}
switch (tag)
{
case ExifTag.ImageDescription:
@ -337,8 +316,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
case ExifTag.GPSDestBearingRef:
case ExifTag.GPSDestDistanceRef:
case ExifTag.GPSDateStamp:
exifValue = new ExifValue(tag, ExifDataType.Ascii, true);
break;
return new ExifValue(tag, ExifDataType.Ascii, value, true);
case ExifTag.ClipPath:
case ExifTag.VersionYear:
@ -351,13 +329,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
case ExifTag.XPKeywords:
case ExifTag.XPSubject:
case ExifTag.GPSVersionID:
exifValue = new ExifValue(tag, ExifDataType.Byte, true);
break;
return new ExifValue(tag, ExifDataType.Byte, value, true);
case ExifTag.FaxProfile:
case ExifTag.ModeNumber:
case ExifTag.GPSAltitudeRef:
exifValue = new ExifValue(tag, ExifDataType.Byte, false);
break;
return new ExifValue(tag, ExifDataType.Byte, value, false);
case ExifTag.FreeOffsets:
case ExifTag.FreeByteCounts:
@ -371,8 +348,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
case ExifTag.StripRowCounts:
case ExifTag.IntergraphRegisters:
case ExifTag.TimeZoneOffset:
exifValue = new ExifValue(tag, ExifDataType.Long, true);
break;
return new ExifValue(tag, ExifDataType.Long, value, true);
case ExifTag.SubfileType:
case ExifTag.SubIFDOffset:
case ExifTag.GPSIFDOffset:
@ -394,8 +370,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
case ExifTag.FaxRecvParams:
case ExifTag.FaxRecvTime:
case ExifTag.ImageNumber:
exifValue = new ExifValue(tag, ExifDataType.Long, false);
break;
return new ExifValue(tag, ExifDataType.Long, value, false);
case ExifTag.WhitePoint:
case ExifTag.PrimaryChromaticities:
@ -410,8 +385,8 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
case ExifTag.GPSTimestamp:
case ExifTag.GPSDestLatitude:
case ExifTag.GPSDestLongitude:
exifValue = new ExifValue(tag, ExifDataType.Rational, true);
break;
return new ExifValue(tag, ExifDataType.Rational, value, true);
case ExifTag.XPosition:
case ExifTag.YPosition:
case ExifTag.XResolution:
@ -445,8 +420,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
case ExifTag.GPSImgDirection:
case ExifTag.GPSDestBearing:
case ExifTag.GPSDestDistance:
exifValue = new ExifValue(tag, ExifDataType.Rational, false);
break;
return new ExifValue(tag, ExifDataType.Rational, value, false);
case ExifTag.BitsPerSample:
case ExifTag.MinSampleValue:
@ -469,8 +443,8 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
case ExifTag.ISOSpeedRatings:
case ExifTag.SubjectArea:
case ExifTag.SubjectLocation:
exifValue = new ExifValue(tag, ExifDataType.Short, true);
break;
return new ExifValue(tag, ExifDataType.Short, value, true);
case ExifTag.OldSubfileType:
case ExifTag.Compression:
case ExifTag.PhotometricInterpretation:
@ -517,20 +491,18 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
case ExifTag.Sharpness:
case ExifTag.SubjectDistanceRange:
case ExifTag.GPSDifferential:
exifValue = new ExifValue(tag, ExifDataType.Short, false);
break;
return new ExifValue(tag, ExifDataType.Short, value, false);
case ExifTag.Decode:
exifValue = new ExifValue(tag, ExifDataType.SignedRational, true);
break;
return new ExifValue(tag, ExifDataType.SignedRational, value, true);
case ExifTag.ShutterSpeedValue:
case ExifTag.BrightnessValue:
case ExifTag.ExposureBiasValue:
case ExifTag.AmbientTemperature:
case ExifTag.WaterDepth:
case ExifTag.CameraElevationAngle:
exifValue = new ExifValue(tag, ExifDataType.SignedRational, false);
break;
return new ExifValue(tag, ExifDataType.SignedRational, value, false);
case ExifTag.JPEGTables:
case ExifTag.OECF:
@ -547,18 +519,17 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
case ExifTag.ImageSourceData:
case ExifTag.GPSProcessingMethod:
case ExifTag.GPSAreaInformation:
exifValue = new ExifValue(tag, ExifDataType.Undefined, true);
break;
return new ExifValue(tag, ExifDataType.Undefined, value, true);
case ExifTag.FileSource:
case ExifTag.SceneType:
exifValue = new ExifValue(tag, ExifDataType.Undefined, false);
break;
return new ExifValue(tag, ExifDataType.Undefined, value, false);
case ExifTag.StripOffsets:
case ExifTag.TileByteCounts:
case ExifTag.ImageLayer:
exifValue = CreateNumber(tag, type, true);
break;
return CreateNumber(tag, value, true);
case ExifTag.ImageWidth:
case ExifTag.ImageLength:
case ExifTag.TileWidth:
@ -567,15 +538,11 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
case ExifTag.ConsecutiveBadFaxLines:
case ExifTag.PixelXDimension:
case ExifTag.PixelYDimension:
exifValue = CreateNumber(tag, type, false);
break;
return CreateNumber(tag, value, false);
default:
throw new NotSupportedException();
}
exifValue.Value = value;
return exifValue;
}
/// <summary>
@ -617,29 +584,35 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// Returns an EXIF value with a numeric type for the given tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="type">The numeric type.</param>
/// <param name="value">The value.</param>
/// <param name="isArray">Whether the value is an array.</param>
/// <returns>
/// The <see cref="ExifValue"/>.
/// </returns>
private static ExifValue CreateNumber(ExifTag tag, Type type, bool isArray)
private static ExifValue CreateNumber(ExifTag tag, object value, bool isArray)
{
Type type = value?.GetType();
if (type != null && type.IsArray)
{
type = type.GetElementType();
}
if (type == null || type == typeof(ushort))
{
return new ExifValue(tag, ExifDataType.Short, isArray);
return new ExifValue(tag, ExifDataType.Short, value, isArray);
}
if (type == typeof(short))
{
return new ExifValue(tag, ExifDataType.SignedShort, isArray);
return new ExifValue(tag, ExifDataType.SignedShort, value, isArray);
}
if (type == typeof(uint))
{
return new ExifValue(tag, ExifDataType.Long, isArray);
return new ExifValue(tag, ExifDataType.Long, value, isArray);
}
return new ExifValue(tag, ExifDataType.SignedLong, isArray);
return new ExifValue(tag, ExifDataType.SignedLong, value, isArray);
}
/// <summary>
@ -770,7 +743,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
private int GetHashCode(ExifValue exif)
{
int hashCode = exif.Tag.GetHashCode() ^ exif.DataType.GetHashCode();
return hashCode ^ exif.exifValue?.GetHashCode() ?? hashCode;
return hashCode ^ exif.Value?.GetHashCode() ?? hashCode;
}
}
}

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

@ -294,18 +294,18 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// Which parts will be written.
/// </summary>
private ExifParts allowedParts;
private Collection<ExifValue> values;
private Collection<int> dataOffsets;
private Collection<int> ifdIndexes;
private Collection<int> exifIndexes;
private Collection<int> gpsIndexes;
private IList<ExifValue> values;
private IList<int> dataOffsets;
private IList<int> ifdIndexes;
private IList<int> exifIndexes;
private IList<int> gpsIndexes;
/// <summary>
/// Initializes a new instance of the <see cref="ExifWriter"/> class.
/// </summary>
/// <param name="values">The values.</param>
/// <param name="allowedParts">The allowed parts.</param>
public ExifWriter(Collection<ExifValue> values, ExifParts allowedParts)
public ExifWriter(IList<ExifValue> values, ExifParts allowedParts)
{
this.values = values;
this.allowedParts = allowedParts;
@ -377,12 +377,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
if (exifLength > 0)
{
this.values[exifIndex].Value = ifdOffset + ifdLength;
this.values[exifIndex] = this.values[exifIndex].WithValue(ifdOffset + ifdLength);
}
if (gpsLength > 0)
{
this.values[gpsIndex].Value = ifdOffset + ifdLength + exifLength;
this.values[gpsIndex] = this.values[gpsIndex].WithValue(ifdOffset + ifdLength + exifLength);
}
i = Write(BitConverter.GetBytes(ifdOffset), result, i);
@ -414,7 +414,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return offset + source.Length;
}
private int GetIndex(Collection<int> indexes, ExifTag tag)
private int GetIndex(IList<int> indexes, ExifTag tag)
{
foreach (int index in indexes)
{
@ -437,7 +437,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return new Collection<int>();
}
Collection<int> result = new Collection<int>();
var result = new Collection<int>();
for (int i = 0; i < this.values.Count; i++)
{
ExifValue value = this.values[i];
@ -457,7 +457,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return result;
}
private uint GetLength(IEnumerable<int> indexes)
private uint GetLength(IList<int> indexes)
{
uint length = 0;
@ -494,7 +494,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return newOffset;
}
private int WriteData(Collection<int> indexes, byte[] destination, int offset)
private int WriteData(IList<int> indexes, byte[] destination, int offset)
{
if (this.dataOffsets.Count == 0)
{
@ -517,9 +517,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return newOffset;
}
private int WriteHeaders(Collection<int> indexes, byte[] destination, int offset)
private int WriteHeaders(IList<int> indexes, byte[] destination, int offset)
{
this.dataOffsets = new Collection<int>();
this.dataOffsets = new List<int>();
int newOffset = Write(BitConverter.GetBytes((ushort)indexes.Count), destination, offset);
@ -550,7 +550,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return newOffset;
}
private int WriteRational(Rational value, byte[] destination, int offset)
private int WriteRational(in Rational value, byte[] destination, int offset)
{
Write(BitConverter.GetBytes(value.Numerator), destination, offset);
Write(BitConverter.GetBytes(value.Denominator), destination, offset + 4);
@ -558,7 +558,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return offset + 8;
}
private int WriteSignedRational(SignedRational value, byte[] destination, int offset)
private int WriteSignedRational(in SignedRational value, byte[] destination, int offset)
{
Write(BitConverter.GetBytes(value.Numerator), destination, offset);
Write(BitConverter.GetBytes(value.Denominator), destination, offset + 4);

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

@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Tests
ExifValue value = image.MetaData.ExifProfile.GetValue(ExifTag.Software);
TestValue(value, "ImageSharp");
Assert.Throws<ArgumentException>(() => { value.Value = 15; });
Assert.Throws<ArgumentException>(() => { value.WithValue(15); });
image.MetaData.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new SignedRational(75.55));
@ -149,7 +149,7 @@ namespace SixLabors.ImageSharp.Tests
TestValue(value, new SignedRational(7555, 100));
Assert.Throws<ArgumentException>(() => { value.Value = 75; });
Assert.Throws<ArgumentException>(() => { value.WithValue(75); });
image.MetaData.ExifProfile.SetValue(ExifTag.XResolution, new Rational(150.0));
@ -159,7 +159,7 @@ namespace SixLabors.ImageSharp.Tests
value = image.MetaData.ExifProfile.GetValue(ExifTag.XResolution);
TestValue(value, new Rational(150, 1));
Assert.Throws<ArgumentException>(() => { value.Value = "ImageSharp"; });
Assert.Throws<ArgumentException>(() => { value.WithValue("ImageSharp"); });
image.MetaData.ExifProfile.SetValue(ExifTag.ReferenceBlackWhite, null);
@ -209,11 +209,11 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void Syncs()
{
ExifProfile exifProfile = new ExifProfile();
var exifProfile = new ExifProfile();
exifProfile.SetValue(ExifTag.XResolution, new Rational(200));
exifProfile.SetValue(ExifTag.YResolution, new Rational(300));
ImageMetaData metaData = new ImageMetaData();
var metaData = new ImageMetaData();
metaData.ExifProfile = exifProfile;
metaData.HorizontalResolution = 200;
metaData.VerticalResolution = 300;

Loading…
Cancel
Save