Browse Source

Merge pull request #2320 from stefannikolei/stefannikolei/nullable/exifprofile

Remove #nullable disable from ExifProfile
pull/2335/head
James Jackson-South 3 years ago
committed by GitHub
parent
commit
22840b37c4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      src/ImageSharp/Common/Helpers/UnitConverter.cs
  2. 7
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  3. 2
      src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs
  4. 34
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  5. 22
      src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
  6. 94
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  7. 2
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  8. 26
      src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
  9. 12
      src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs
  10. 10
      src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs
  11. 89
      src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
  12. 28
      src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
  13. 23
      src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs
  14. 37
      src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs
  15. 7
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifArrayValue{TValueType}.cs
  16. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByte.cs
  17. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs
  18. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDouble.cs
  19. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs
  20. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloat.cs
  21. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong.cs
  22. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs
  23. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs
  24. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs
  25. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs
  26. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRational.cs
  27. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRationalArray.cs
  28. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShort.cs
  29. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShortArray.cs
  30. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByte.cs
  31. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShort.cs
  32. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs
  33. 4
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifString.cs
  34. 6
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifUcs2String.cs
  35. 11
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs
  36. 11
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs
  37. 20
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue{TValueType}.cs
  38. 4
      src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue.cs
  39. 2
      src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue{TValueType}.cs
  40. 3
      src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs
  41. 4
      src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs
  42. 1
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  43. 1
      tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs
  44. 1
      tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs
  45. 1
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs
  46. 9
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  47. 1
      tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
  48. 1
      tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs
  49. 12
      tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs
  50. 1
      tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs
  51. 4
      tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs
  52. 1
      tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs
  53. 18
      tests/ImageSharp.Tests/TestUtilities/ExifProfileExtensions.cs

9
src/ImageSharp/Common/Helpers/UnitConverter.cs

@ -91,10 +91,13 @@ internal static class UnitConverter
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static PixelResolutionUnit ExifProfileToResolutionUnit(ExifProfile profile) public static PixelResolutionUnit ExifProfileToResolutionUnit(ExifProfile profile)
{ {
IExifValue<ushort> resolution = profile.GetValue(ExifTag.ResolutionUnit); if (profile.TryGetValue(ExifTag.ResolutionUnit, out IExifValue<ushort>? resolution))
{
// EXIF is 1, 2, 3 so we minus "1" off the result.
return (PixelResolutionUnit)(byte)(resolution.Value - 1);
}
// EXIF is 1, 2, 3 so we minus "1" off the result. return DefaultResolutionUnit;
return resolution is null ? DefaultResolutionUnit : (PixelResolutionUnit)(byte)(resolution.Value - 1);
} }
/// <summary> /// <summary>

7
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -717,9 +717,12 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
private double GetExifResolutionValue(ExifTag<Rational> tag) private double GetExifResolutionValue(ExifTag<Rational> tag)
{ {
IExifValue<Rational> resolution = this.Metadata.ExifProfile.GetValue(tag); if (this.Metadata.ExifProfile.TryGetValue(tag, out IExifValue<Rational> resolution))
{
return resolution.Value.ToDouble();
}
return resolution is null ? 0 : resolution.Value.ToDouble(); return 0;
} }
/// <summary> /// <summary>

2
src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs

@ -82,7 +82,7 @@ public readonly struct TiffBitsPerSample : IEquatable<TiffBitsPerSample>
/// <param name="value">The value to parse.</param> /// <param name="value">The value to parse.</param>
/// <param name="sample">The tiff bits per sample.</param> /// <param name="sample">The tiff bits per sample.</param>
/// <returns>True, if the value could be parsed.</returns> /// <returns>True, if the value could be parsed.</returns>
public static bool TryParse(ushort[] value, out TiffBitsPerSample sample) public static bool TryParse(ushort[]? value, out TiffBitsPerSample sample)
{ {
if (value is null || value.Length == 0) if (value is null || value.Length == 0)
{ {

34
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -276,9 +276,15 @@ internal class TiffDecoderCore : IImageDecoderInternals
private void DecodeImageWithStrips<TPixel>(ExifProfile tags, ImageFrame<TPixel> frame, CancellationToken cancellationToken) private void DecodeImageWithStrips<TPixel>(ExifProfile tags, ImageFrame<TPixel> frame, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int rowsPerStrip = tags.GetValue(ExifTag.RowsPerStrip) != null int rowsPerStrip;
? (int)tags.GetValue(ExifTag.RowsPerStrip).Value if (tags.TryGetValue(ExifTag.RowsPerStrip, out IExifValue<Number> value))
: TiffConstants.RowsPerStripInfinity; {
rowsPerStrip = (int)value.Value;
}
else
{
rowsPerStrip = TiffConstants.RowsPerStripInfinity;
}
Array stripOffsetsArray = (Array)tags.GetValueInternal(ExifTag.StripOffsets).GetValue(); Array stripOffsetsArray = (Array)tags.GetValueInternal(ExifTag.StripOffsets).GetValue();
Array stripByteCountsArray = (Array)tags.GetValueInternal(ExifTag.StripByteCounts).GetValue(); Array stripByteCountsArray = (Array)tags.GetValueInternal(ExifTag.StripByteCounts).GetValue();
@ -320,8 +326,18 @@ internal class TiffDecoderCore : IImageDecoderInternals
int width = pixels.Width; int width = pixels.Width;
int height = pixels.Height; int height = pixels.Height;
int tileWidth = (int)tags.GetValue(ExifTag.TileWidth).Value; if (!tags.TryGetValue(ExifTag.TileWidth, out IExifValue<Number> valueWidth))
int tileLength = (int)tags.GetValue(ExifTag.TileLength).Value; {
ArgumentNullException.ThrowIfNull(valueWidth);
}
if (!tags.TryGetValue(ExifTag.TileWidth, out IExifValue<Number> valueLength))
{
ArgumentNullException.ThrowIfNull(valueLength);
}
int tileWidth = (int)valueWidth.Value;
int tileLength = (int)valueLength.Value;
int tilesAcross = (width + tileWidth - 1) / tileWidth; int tilesAcross = (width + tileWidth - 1) / tileWidth;
int tilesDown = (height + tileLength - 1) / tileLength; int tilesDown = (height + tileLength - 1) / tileLength;
@ -775,10 +791,9 @@ internal class TiffDecoderCore : IImageDecoderInternals
/// <returns>The image width.</returns> /// <returns>The image width.</returns>
private static int GetImageWidth(ExifProfile exifProfile) private static int GetImageWidth(ExifProfile exifProfile)
{ {
IExifValue<Number> width = exifProfile.GetValue(ExifTag.ImageWidth); if (!exifProfile.TryGetValue(ExifTag.ImageWidth, out IExifValue<Number> width))
if (width == null)
{ {
TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageWidth"); TiffThrowHelper.ThrowInvalidImageContentException("The TIFF image frame is missing the ImageWidth");
} }
DebugGuard.MustBeLessThanOrEqualTo((ulong)width.Value, (ulong)int.MaxValue, nameof(ExifTag.ImageWidth)); DebugGuard.MustBeLessThanOrEqualTo((ulong)width.Value, (ulong)int.MaxValue, nameof(ExifTag.ImageWidth));
@ -793,8 +808,7 @@ internal class TiffDecoderCore : IImageDecoderInternals
/// <returns>The image height.</returns> /// <returns>The image height.</returns>
private static int GetImageHeight(ExifProfile exifProfile) private static int GetImageHeight(ExifProfile exifProfile)
{ {
IExifValue<Number> height = exifProfile.GetValue(ExifTag.ImageLength); if (!exifProfile.TryGetValue(ExifTag.ImageLength, out IExifValue<Number> height))
if (height == null)
{ {
TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageLength"); TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageLength");
} }

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

@ -38,14 +38,12 @@ internal static class TiffDecoderMetadataCreator
frameMetaData.IptcProfile = new IptcProfile(iptcBytes); frameMetaData.IptcProfile = new IptcProfile(iptcBytes);
} }
IExifValue<byte[]> xmpProfileBytes = frameMetaData.ExifProfile.GetValue(ExifTag.XMP); if (frameMetaData.ExifProfile.TryGetValue(ExifTag.XMP, out IExifValue<byte[]> xmpProfileBytes))
if (xmpProfileBytes != null)
{ {
frameMetaData.XmpProfile = new XmpProfile(xmpProfileBytes.Value); frameMetaData.XmpProfile = new XmpProfile(xmpProfileBytes.Value);
} }
IExifValue<byte[]> iccProfileBytes = frameMetaData.ExifProfile.GetValue(ExifTag.IccProfile); if (frameMetaData.ExifProfile.TryGetValue(ExifTag.IccProfile, out IExifValue<byte[]> iccProfileBytes))
if (iccProfileBytes != null)
{ {
frameMetaData.IccProfile = new IccProfile(iccProfileBytes.Value); frameMetaData.IccProfile = new IccProfile(iccProfileBytes.Value);
} }
@ -70,16 +68,20 @@ internal static class TiffDecoderMetadataCreator
private static void SetResolution(ImageMetadata imageMetaData, ExifProfile exifProfile) private static void SetResolution(ImageMetadata imageMetaData, ExifProfile exifProfile)
{ {
imageMetaData.ResolutionUnits = exifProfile != null ? UnitConverter.ExifProfileToResolutionUnit(exifProfile) : PixelResolutionUnit.PixelsPerInch; imageMetaData.ResolutionUnits = exifProfile != null ? UnitConverter.ExifProfileToResolutionUnit(exifProfile) : PixelResolutionUnit.PixelsPerInch;
double? horizontalResolution = exifProfile?.GetValue(ExifTag.XResolution)?.Value.ToDouble();
if (horizontalResolution != null) if (exifProfile is null)
{
return;
}
if (exifProfile.TryGetValue(ExifTag.XResolution, out IExifValue<Rational> horizontalResolution))
{ {
imageMetaData.HorizontalResolution = horizontalResolution.Value; imageMetaData.HorizontalResolution = horizontalResolution.Value.ToDouble();
} }
double? verticalResolution = exifProfile?.GetValue(ExifTag.YResolution)?.Value.ToDouble(); if (exifProfile.TryGetValue(ExifTag.YResolution, out IExifValue<Rational> verticalResolution))
if (verticalResolution != null)
{ {
imageMetaData.VerticalResolution = verticalResolution.Value; imageMetaData.VerticalResolution = verticalResolution.Value.ToDouble();
} }
} }

94
src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

@ -42,7 +42,16 @@ internal static class TiffDecoderOptionsParser
} }
} }
TiffFillOrder fillOrder = (TiffFillOrder?)exifProfile.GetValue(ExifTag.FillOrder)?.Value ?? TiffFillOrder.MostSignificantBitFirst; TiffFillOrder fillOrder;
if (exifProfile.TryGetValue(ExifTag.FillOrder, out IExifValue<ushort> value))
{
fillOrder = (TiffFillOrder)value.Value;
}
else
{
fillOrder = TiffFillOrder.MostSignificantBitFirst;
}
if (fillOrder == TiffFillOrder.LeastSignificantBitFirst && frameMetadata.BitsPerPixel != TiffBitsPerPixel.Bit1) if (fillOrder == TiffFillOrder.LeastSignificantBitFirst && frameMetadata.BitsPerPixel != TiffBitsPerPixel.Bit1)
{ {
TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is only supported in combination with 1bit per pixel bicolor tiff's."); TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is only supported in combination with 1bit per pixel bicolor tiff's.");
@ -53,10 +62,10 @@ internal static class TiffDecoderOptionsParser
TiffThrowHelper.ThrowNotSupported("TIFF images with FloatingPoint horizontal predictor are not supported."); TiffThrowHelper.ThrowNotSupported("TIFF images with FloatingPoint horizontal predictor are not supported.");
} }
TiffSampleFormat[] sampleFormats = exifProfile.GetValue(ExifTag.SampleFormat)?.Value?.Select(a => (TiffSampleFormat)a).ToArray();
TiffSampleFormat? sampleFormat = null; TiffSampleFormat? sampleFormat = null;
if (sampleFormats != null) if (exifProfile.TryGetValue(ExifTag.SampleFormat, out var formatValue))
{ {
TiffSampleFormat[] sampleFormats = formatValue.Value.Select(a => (TiffSampleFormat)a).ToArray();
sampleFormat = sampleFormats[0]; sampleFormat = sampleFormats[0];
foreach (TiffSampleFormat format in sampleFormats) foreach (TiffSampleFormat format in sampleFormats)
{ {
@ -67,7 +76,12 @@ internal static class TiffDecoderOptionsParser
} }
} }
ushort[] ycbcrSubSampling = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value; ushort[] ycbcrSubSampling = null;
if (exifProfile.TryGetValue(ExifTag.YCbCrSubsampling, out IExifValue<ushort[]> subSamplingValue))
{
ycbcrSubSampling = subSamplingValue.Value;
}
if (ycbcrSubSampling != null && ycbcrSubSampling.Length != 2) if (ycbcrSubSampling != null && ycbcrSubSampling.Length != 2)
{ {
TiffThrowHelper.ThrowImageFormatException("Invalid YCbCrSubsampling, expected 2 values."); TiffThrowHelper.ThrowImageFormatException("Invalid YCbCrSubsampling, expected 2 values.");
@ -78,23 +92,52 @@ internal static class TiffDecoderOptionsParser
TiffThrowHelper.ThrowImageFormatException("ChromaSubsampleVert shall always be less than or equal to ChromaSubsampleHoriz."); TiffThrowHelper.ThrowImageFormatException("ChromaSubsampleVert shall always be less than or equal to ChromaSubsampleHoriz.");
} }
if (exifProfile.GetValue(ExifTag.StripRowCounts)?.Value != null) if (exifProfile.TryGetValue(ExifTag.StripRowCounts, out _))
{ {
TiffThrowHelper.ThrowNotSupported("Variable-sized strips are not supported."); TiffThrowHelper.ThrowNotSupported("Variable-sized strips are not supported.");
} }
options.PlanarConfiguration = (TiffPlanarConfiguration?)exifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value ?? DefaultPlanarConfiguration; if (exifProfile.TryGetValue(ExifTag.PlanarConfiguration, out IExifValue<ushort> planarValue))
{
options.PlanarConfiguration = (TiffPlanarConfiguration)planarValue.Value;
}
else
{
options.PlanarConfiguration = DefaultPlanarConfiguration;
}
options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None; options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None;
options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb; options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb;
options.SampleFormat = sampleFormat ?? TiffSampleFormat.UnsignedInteger; options.SampleFormat = sampleFormat ?? TiffSampleFormat.UnsignedInteger;
options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24; options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24;
options.BitsPerSample = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0); options.BitsPerSample = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0);
options.ReferenceBlackAndWhite = exifProfile.GetValue(ExifTag.ReferenceBlackWhite)?.Value;
options.YcbcrCoefficients = exifProfile.GetValue(ExifTag.YCbCrCoefficients)?.Value; if (exifProfile.TryGetValue(ExifTag.ReferenceBlackWhite, out IExifValue<Rational[]> blackWhiteValue))
options.YcbcrSubSampling = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value; {
options.ReferenceBlackAndWhite = blackWhiteValue.Value;
}
if (exifProfile.TryGetValue(ExifTag.YCbCrCoefficients, out IExifValue<Rational[]> coefficientsValue))
{
options.YcbcrCoefficients = coefficientsValue.Value;
}
if (exifProfile.TryGetValue(ExifTag.YCbCrSubsampling, out IExifValue<ushort[]> ycbrSubSamplingValue))
{
options.YcbcrSubSampling = ycbrSubSamplingValue.Value;
}
options.FillOrder = fillOrder; options.FillOrder = fillOrder;
options.JpegTables = exifProfile.GetValue(ExifTag.JPEGTables)?.Value;
options.OldJpegCompressionStartOfImageMarker = exifProfile.GetValue(ExifTag.JPEGInterchangeFormat)?.Value; if (exifProfile.TryGetValue(ExifTag.JPEGTables, out IExifValue<byte[]> jpegTablesValue))
{
options.JpegTables = jpegTablesValue.Value;
}
if (exifProfile.TryGetValue(ExifTag.JPEGInterchangeFormat, out IExifValue<uint> jpegInterchangeFormatValue))
{
options.OldJpegCompressionStartOfImageMarker = jpegInterchangeFormatValue.Value;
}
options.ParseColorType(exifProfile); options.ParseColorType(exifProfile);
options.ParseCompression(frameMetadata.Compression, exifProfile); options.ParseCompression(frameMetadata.Compression, exifProfile);
@ -394,9 +437,9 @@ internal static class TiffDecoderOptionsParser
case TiffPhotometricInterpretation.PaletteColor: case TiffPhotometricInterpretation.PaletteColor:
{ {
options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; if (exifProfile.TryGetValue(ExifTag.ColorMap, out IExifValue<ushort[]> value))
if (options.ColorMap != null)
{ {
options.ColorMap = value.Value;
if (options.BitsPerSample.Channels != 1) if (options.BitsPerSample.Channels != 1)
{ {
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
@ -414,7 +457,11 @@ internal static class TiffDecoderOptionsParser
case TiffPhotometricInterpretation.YCbCr: case TiffPhotometricInterpretation.YCbCr:
{ {
options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; if (exifProfile.TryGetValue(ExifTag.ColorMap, out IExifValue<ushort[]> value))
{
options.ColorMap = value.Value;
}
if (options.BitsPerSample.Channels != 3) if (options.BitsPerSample.Channels != 3)
{ {
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported for YCbCr images."); TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported for YCbCr images.");
@ -508,7 +555,15 @@ internal static class TiffDecoderOptionsParser
case TiffCompression.CcittGroup3Fax: case TiffCompression.CcittGroup3Fax:
{ {
options.CompressionType = TiffDecoderCompressionType.T4; options.CompressionType = TiffDecoderCompressionType.T4;
options.FaxCompressionOptions = exifProfile.GetValue(ExifTag.T4Options) != null ? (FaxCompressionOptions)exifProfile.GetValue(ExifTag.T4Options).Value : FaxCompressionOptions.None;
if (exifProfile.TryGetValue(ExifTag.T4Options, out IExifValue<uint> t4OptionsValue))
{
options.FaxCompressionOptions = (FaxCompressionOptions)t4OptionsValue.Value;
}
else
{
options.FaxCompressionOptions = FaxCompressionOptions.None;
}
break; break;
} }
@ -516,7 +571,14 @@ internal static class TiffDecoderOptionsParser
case TiffCompression.CcittGroup4Fax: case TiffCompression.CcittGroup4Fax:
{ {
options.CompressionType = TiffDecoderCompressionType.T6; options.CompressionType = TiffDecoderCompressionType.T6;
options.FaxCompressionOptions = exifProfile.GetValue(ExifTag.T4Options) != null ? (FaxCompressionOptions)exifProfile.GetValue(ExifTag.T4Options).Value : FaxCompressionOptions.None; if (exifProfile.TryGetValue(ExifTag.T4Options, out IExifValue<uint> t4OptionsValue))
{
options.FaxCompressionOptions = (FaxCompressionOptions)t4OptionsValue.Value;
}
else
{
options.FaxCompressionOptions = FaxCompressionOptions.None;
}
break; break;
} }

2
src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

@ -164,8 +164,6 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
TiffNewSubfileType subfileType = (TiffNewSubfileType)(frame.Metadata.ExifProfile?.GetValue(ExifTag.SubfileType)?.Value ?? (int)TiffNewSubfileType.FullImage);
ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, ifdMarker); ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, ifdMarker);
metadataImage = null; metadataImage = null;
} }

26
src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
#nullable disable
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -78,15 +77,30 @@ public class TiffFrameMetadata : IDeepCloneable
{ {
if (profile != null) if (profile != null)
{ {
if (TiffBitsPerSample.TryParse(profile.GetValue(ExifTag.BitsPerSample)?.Value, out TiffBitsPerSample bitsPerSample)) if (profile.TryGetValue(ExifTag.BitsPerSample, out IExifValue<ushort[]>? bitsPerSampleValue))
{ {
meta.BitsPerSample = bitsPerSample; if (TiffBitsPerSample.TryParse(bitsPerSampleValue.Value, out TiffBitsPerSample bitsPerSample))
{
meta.BitsPerSample = bitsPerSample;
}
} }
meta.BitsPerPixel = meta.BitsPerSample?.BitsPerPixel(); meta.BitsPerPixel = meta.BitsPerSample?.BitsPerPixel();
meta.Compression = (TiffCompression?)profile.GetValue(ExifTag.Compression)?.Value;
meta.PhotometricInterpretation = (TiffPhotometricInterpretation?)profile.GetValue(ExifTag.PhotometricInterpretation)?.Value; if (profile.TryGetValue(ExifTag.Compression, out IExifValue<ushort>? compressionValue))
meta.Predictor = (TiffPredictor?)profile.GetValue(ExifTag.Predictor)?.Value; {
meta.Compression = (TiffCompression)compressionValue.Value;
}
if (profile.TryGetValue(ExifTag.PhotometricInterpretation, out IExifValue<ushort>? photometricInterpretationValue))
{
meta.PhotometricInterpretation = (TiffPhotometricInterpretation)photometricInterpretationValue.Value;
}
if (profile.TryGetValue(ExifTag.Predictor, out IExifValue<ushort>? predictorValue))
{
meta.Predictor = (TiffPredictor)predictorValue.Value;
}
profile.RemoveValue(ExifTag.BitsPerSample); profile.RemoveValue(ExifTag.BitsPerSample);
profile.RemoveValue(ExifTag.Compression); profile.RemoveValue(ExifTag.Compression);

12
src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs

@ -1,21 +1,33 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Formats.Tiff; namespace SixLabors.ImageSharp.Formats.Tiff;
internal static class TiffThrowHelper internal static class TiffThrowHelper
{ {
[DoesNotReturn]
public static Exception ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage); public static Exception ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage);
[DoesNotReturn]
public static Exception ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);
[DoesNotReturn]
public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}"); public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}");
[DoesNotReturn]
public static Exception NotSupportedCompressor(string compressionType) => throw new NotSupportedException($"Not supported encoder compression method: {compressionType}"); public static Exception NotSupportedCompressor(string compressionType) => throw new NotSupportedException($"Not supported encoder compression method: {compressionType}");
[DoesNotReturn]
public static Exception InvalidColorType(string colorType) => throw new NotSupportedException($"Invalid color type: {colorType}"); public static Exception InvalidColorType(string colorType) => throw new NotSupportedException($"Invalid color type: {colorType}");
[DoesNotReturn]
public static Exception ThrowInvalidHeader() => throw new ImageFormatException("Invalid TIFF file header."); public static Exception ThrowInvalidHeader() => throw new ImageFormatException("Invalid TIFF file header.");
[DoesNotReturn]
public static void ThrowNotSupported(string message) => throw new NotSupportedException(message); public static void ThrowNotSupported(string message) => throw new NotSupportedException(message);
[DoesNotReturn]
public static void ThrowArgumentException(string message) => throw new ArgumentException(message); public static void ThrowArgumentException(string message) => throw new ArgumentException(message);
} }

10
src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Text; using System.Text;
@ -27,7 +26,14 @@ internal static class ExifEncodedStringHelpers
// 20932 EUC-JP Japanese (JIS 0208-1990 and 0212-1990) // 20932 EUC-JP Japanese (JIS 0208-1990 and 0212-1990)
// https://docs.microsoft.com/en-us/dotnet/api/system.text.encoding?view=net-6.0 // https://docs.microsoft.com/en-us/dotnet/api/system.text.encoding?view=net-6.0
private static Encoding JIS0208Encoding => CodePagesEncodingProvider.Instance.GetEncoding(20932); private static Encoding JIS0208Encoding
{
get
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
return Encoding.GetEncoding(20932);
}
}
public static bool IsEncodedString(ExifTagValue tag) => tag switch public static bool IsEncodedString(ExifTagValue tag) => tag switch
{ {

89
src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
#nullable disable
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -14,12 +14,12 @@ public sealed class ExifProfile : IDeepCloneable<ExifProfile>
/// <summary> /// <summary>
/// The byte array to read the EXIF profile from. /// The byte array to read the EXIF profile from.
/// </summary> /// </summary>
private readonly byte[] data; private readonly byte[]? data;
/// <summary> /// <summary>
/// The collection of EXIF values /// The collection of EXIF values
/// </summary> /// </summary>
private List<IExifValue> values; private List<IExifValue>? values;
/// <summary> /// <summary>
/// The thumbnail offset position in the byte stream /// The thumbnail offset position in the byte stream
@ -35,7 +35,7 @@ public sealed class ExifProfile : IDeepCloneable<ExifProfile>
/// Initializes a new instance of the <see cref="ExifProfile"/> class. /// Initializes a new instance of the <see cref="ExifProfile"/> class.
/// </summary> /// </summary>
public ExifProfile() public ExifProfile()
: this((byte[])null) : this((byte[]?)null)
{ {
} }
@ -43,7 +43,7 @@ public sealed class ExifProfile : IDeepCloneable<ExifProfile>
/// Initializes a new instance of the <see cref="ExifProfile"/> class. /// Initializes a new instance of the <see cref="ExifProfile"/> class.
/// </summary> /// </summary>
/// <param name="data">The byte array to read the EXIF profile from.</param> /// <param name="data">The byte array to read the EXIF profile from.</param>
public ExifProfile(byte[] data) public ExifProfile(byte[]? data)
{ {
this.Parts = ExifParts.All; this.Parts = ExifParts.All;
this.data = data; this.data = data;
@ -78,7 +78,7 @@ public sealed class ExifProfile : IDeepCloneable<ExifProfile>
this.InvalidTags = other.InvalidTags.Count > 0 this.InvalidTags = other.InvalidTags.Count > 0
? new List<ExifTag>(other.InvalidTags) ? new List<ExifTag>(other.InvalidTags)
: (IReadOnlyList<ExifTag>)Array.Empty<ExifTag>(); : Array.Empty<ExifTag>();
if (other.values != null) if (other.values != null)
{ {
@ -110,6 +110,7 @@ public sealed class ExifProfile : IDeepCloneable<ExifProfile>
/// <summary> /// <summary>
/// Gets the values of this EXIF profile. /// Gets the values of this EXIF profile.
/// </summary> /// </summary>
[MemberNotNull(nameof(values))]
public IReadOnlyList<IExifValue> Values public IReadOnlyList<IExifValue> Values
{ {
get get
@ -122,36 +123,47 @@ public sealed class ExifProfile : IDeepCloneable<ExifProfile>
/// <summary> /// <summary>
/// Returns the thumbnail in the EXIF profile when available. /// Returns the thumbnail in the EXIF profile when available.
/// </summary> /// </summary>
/// <param name="image">The thumbnail</param>
/// <returns> /// <returns>
/// The <see cref="Image"/>. /// True, if there is a thumbnail otherwise false.
/// </returns> /// </returns>
public Image CreateThumbnail() => this.CreateThumbnail<Rgba32>(); public bool TryCreateThumbnail([NotNullWhen(true)] out Image? image)
{
if (this.TryCreateThumbnail(out Image<Rgba32>? innerimage))
{
image = innerimage;
return true;
}
image = null;
return false;
}
/// <summary> /// <summary>
/// Returns the thumbnail in the EXIF profile when available. /// Returns the thumbnail in the EXIF profile when available.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns> /// <param name="image">The thumbnail.</param>
/// The <see cref="Image{TPixel}"/>. /// <returns>True, if there is a thumbnail otherwise false.</returns>
/// </returns> public bool TryCreateThumbnail<TPixel>([NotNullWhen(true)] out Image<TPixel>? image)
public Image<TPixel> CreateThumbnail<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
this.InitializeValues(); this.InitializeValues();
image = null;
if (this.thumbnailOffset == 0 || this.thumbnailLength == 0) if (this.thumbnailOffset == 0 || this.thumbnailLength == 0)
{ {
return null; return false;
} }
if (this.data is null || this.data.Length < (this.thumbnailOffset + this.thumbnailLength)) if (this.data is null || this.data.Length < (this.thumbnailOffset + this.thumbnailLength))
{ {
return null; return false;
} }
using (var memStream = new MemoryStream(this.data, this.thumbnailOffset, this.thumbnailLength)) using (MemoryStream memStream = new(this.data, this.thumbnailOffset, this.thumbnailLength))
{ {
return Image.Load<TPixel>(memStream); image = Image.Load<TPixel>(memStream);
return true;
} }
} }
@ -159,12 +171,21 @@ public sealed class ExifProfile : IDeepCloneable<ExifProfile>
/// Returns the value with the specified tag. /// Returns the value with the specified tag.
/// </summary> /// </summary>
/// <param name="tag">The tag of the exif value.</param> /// <param name="tag">The tag of the exif value.</param>
/// <returns>The value with the specified tag.</returns> /// <param name="exifValue">The value with the specified tag.</param>
/// <returns>True when found, otherwise false</returns>
/// <typeparam name="TValueType">The data type of the tag.</typeparam> /// <typeparam name="TValueType">The data type of the tag.</typeparam>
public IExifValue<TValueType> GetValue<TValueType>(ExifTag<TValueType> tag) public bool TryGetValue<TValueType>(ExifTag<TValueType> tag, [NotNullWhen(true)] out IExifValue<TValueType>? exifValue)
{ {
IExifValue value = this.GetValueInternal(tag); IExifValue? value = this.GetValueInternal(tag);
return value is null ? null : (IExifValue<TValueType>)value;
if (value is null)
{
exifValue = null;
return false;
}
exifValue = (IExifValue<TValueType>)value;
return true;
} }
/// <summary> /// <summary>
@ -203,7 +224,7 @@ public sealed class ExifProfile : IDeepCloneable<ExifProfile>
/// Converts this instance to a byte array. /// Converts this instance to a byte array.
/// </summary> /// </summary>
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
public byte[] ToByteArray() public byte[]? ToByteArray()
{ {
if (this.values is null) if (this.values is null)
{ {
@ -215,19 +236,19 @@ public sealed class ExifProfile : IDeepCloneable<ExifProfile>
return Array.Empty<byte>(); return Array.Empty<byte>();
} }
var writer = new ExifWriter(this.values, this.Parts); ExifWriter writer = new(this.values, this.Parts);
return writer.GetData(); return writer.GetData();
} }
/// <inheritdoc/> /// <inheritdoc/>
public ExifProfile DeepClone() => new ExifProfile(this); public ExifProfile DeepClone() => new(this);
/// <summary> /// <summary>
/// Returns the value with the specified tag. /// Returns the value with the specified tag.
/// </summary> /// </summary>
/// <param name="tag">The tag of the exif value.</param> /// <param name="tag">The tag of the exif value.</param>
/// <returns>The value with the specified tag.</returns> /// <returns>The value with the specified tag.</returns>
internal IExifValue GetValueInternal(ExifTag tag) internal IExifValue? GetValueInternal(ExifTag tag)
{ {
foreach (IExifValue exifValue in this.Values) foreach (IExifValue exifValue in this.Values)
{ {
@ -245,7 +266,8 @@ public sealed class ExifProfile : IDeepCloneable<ExifProfile>
/// </summary> /// </summary>
/// <param name="tag">The tag of the exif value.</param> /// <param name="tag">The tag of the exif value.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
internal void SetValueInternal(ExifTag tag, object value) /// <exception cref="NotSupportedException">Newly created value is null.</exception>
internal void SetValueInternal(ExifTag tag, object? value)
{ {
foreach (IExifValue exifValue in this.Values) foreach (IExifValue exifValue in this.Values)
{ {
@ -256,10 +278,10 @@ public sealed class ExifProfile : IDeepCloneable<ExifProfile>
} }
} }
ExifValue newExifValue = ExifValues.Create(tag); ExifValue? newExifValue = ExifValues.Create(tag);
if (newExifValue is null) if (newExifValue is null)
{ {
throw new NotSupportedException(); throw new NotSupportedException($"Newly created value for tag {tag} is null.");
} }
newExifValue.TrySetValue(value); newExifValue.TrySetValue(value);
@ -278,9 +300,7 @@ public sealed class ExifProfile : IDeepCloneable<ExifProfile>
private void SyncResolution(ExifTag<Rational> tag, double resolution) private void SyncResolution(ExifTag<Rational> tag, double resolution)
{ {
IExifValue<Rational> value = this.GetValue(tag); if (!this.TryGetValue(tag, out IExifValue<Rational>? value))
if (value is null)
{ {
return; return;
} }
@ -290,10 +310,11 @@ public sealed class ExifProfile : IDeepCloneable<ExifProfile>
this.RemoveValue(value.Tag); this.RemoveValue(value.Tag);
} }
var newResolution = new Rational(resolution, false); Rational newResolution = new(resolution, false);
this.SetValue(tag, newResolution); this.SetValue(tag, newResolution);
} }
[MemberNotNull(nameof(values))]
private void InitializeValues() private void InitializeValues()
{ {
if (this.values != null) if (this.values != null)
@ -307,13 +328,13 @@ public sealed class ExifProfile : IDeepCloneable<ExifProfile>
return; return;
} }
var reader = new ExifReader(this.data); ExifReader reader = new(this.data);
this.values = reader.ReadValues(); this.values = reader.ReadValues();
this.InvalidTags = reader.InvalidTags.Count > 0 this.InvalidTags = reader.InvalidTags.Count > 0
? new List<ExifTag>(reader.InvalidTags) ? new List<ExifTag>(reader.InvalidTags)
: (IReadOnlyList<ExifTag>)Array.Empty<ExifTag>(); : Array.Empty<ExifTag>();
this.thumbnailOffset = (int)reader.ThumbnailOffset; this.thumbnailOffset = (int)reader.ThumbnailOffset;
this.thumbnailLength = (int)reader.ThumbnailLength; this.thumbnailLength = (int)reader.ThumbnailLength;

28
src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers; using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
@ -20,9 +19,10 @@ internal class ExifReader : BaseExifReader
{ {
} }
public ExifReader(byte[] exifData, MemoryAllocator allocator) public ExifReader(byte[] exifData, MemoryAllocator? allocator)
: base(new MemoryStream(exifData ?? throw new ArgumentNullException(nameof(exifData))), allocator) : base(new MemoryStream(exifData ?? throw new ArgumentNullException(nameof(exifData))), allocator)
{ {
// TODO: We never call this constructor passing a non-null allocator.
} }
/// <summary> /// <summary>
@ -90,13 +90,13 @@ internal abstract class BaseExifReader
private readonly byte[] buf4 = new byte[4]; private readonly byte[] buf4 = new byte[4];
private readonly byte[] buf2 = new byte[2]; private readonly byte[] buf2 = new byte[2];
private readonly MemoryAllocator allocator; private readonly MemoryAllocator? allocator;
private readonly Stream data; private readonly Stream data;
private List<ExifTag> invalidTags; private List<ExifTag>? invalidTags;
private List<ulong> subIfds; private List<ulong>? subIfds;
protected BaseExifReader(Stream stream, MemoryAllocator allocator) protected BaseExifReader(Stream stream, MemoryAllocator? allocator)
{ {
this.data = stream ?? throw new ArgumentNullException(nameof(stream)); this.data = stream ?? throw new ArgumentNullException(nameof(stream));
this.allocator = allocator; this.allocator = allocator;
@ -219,7 +219,7 @@ internal abstract class BaseExifReader
this.Seek(tag.Offset); this.Seek(tag.Offset);
if (this.TryReadSpan(buffer)) if (this.TryReadSpan(buffer))
{ {
object value = this.ConvertValue(tag.DataType, buffer, tag.NumberOfComponents > 1 || tag.Exif.IsArray); object? value = this.ConvertValue(tag.DataType, buffer, tag.NumberOfComponents > 1 || tag.Exif.IsArray);
this.Add(values, tag.Exif, value); this.Add(values, tag.Exif, value);
} }
} }
@ -255,7 +255,7 @@ internal abstract class BaseExifReader
private static byte ConvertToByte(ReadOnlySpan<byte> buffer) => buffer[0]; private static byte ConvertToByte(ReadOnlySpan<byte> buffer) => buffer[0];
private object ConvertValue(ExifDataType dataType, ReadOnlySpan<byte> buffer, bool isArray) private object? ConvertValue(ExifDataType dataType, ReadOnlySpan<byte> buffer, bool isArray)
{ {
if (buffer.Length == 0) if (buffer.Length == 0)
{ {
@ -390,7 +390,7 @@ internal abstract class BaseExifReader
numberOfComponents = 4 / ExifDataTypes.GetSize(dataType); numberOfComponents = 4 / ExifDataTypes.GetSize(dataType);
} }
ExifValue exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); ExifValue? exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents);
if (exifValue is null) if (exifValue is null)
{ {
@ -414,7 +414,7 @@ internal abstract class BaseExifReader
} }
else else
{ {
object value = this.ConvertValue(dataType, offsetBuffer[..(int)size], numberOfComponents > 1 || exifValue.IsArray); object? value = this.ConvertValue(dataType, offsetBuffer[..(int)size], numberOfComponents > 1 || exifValue.IsArray);
this.Add(values, exifValue, value); this.Add(values, exifValue, value);
} }
} }
@ -443,7 +443,7 @@ internal abstract class BaseExifReader
numberOfComponents = 8 / ExifDataTypes.GetSize(dataType); numberOfComponents = 8 / ExifDataTypes.GetSize(dataType);
} }
ExifValue exifValue = tag switch ExifValue? exifValue = tag switch
{ {
ExifTagValue.StripOffsets => new ExifLong8Array(ExifTagValue.StripOffsets), ExifTagValue.StripOffsets => new ExifLong8Array(ExifTagValue.StripOffsets),
ExifTagValue.StripByteCounts => new ExifLong8Array(ExifTagValue.StripByteCounts), ExifTagValue.StripByteCounts => new ExifLong8Array(ExifTagValue.StripByteCounts),
@ -471,12 +471,12 @@ internal abstract class BaseExifReader
} }
else else
{ {
object value = this.ConvertValue(dataType, offsetBuffer[..(int)size], numberOfComponents > 1 || exifValue.IsArray); object? value = this.ConvertValue(dataType, offsetBuffer[..(int)size], numberOfComponents > 1 || exifValue.IsArray);
this.Add(values, exifValue, value); this.Add(values, exifValue, value);
} }
} }
private void Add(IList<IExifValue> values, IExifValue exif, object value) private void Add(IList<IExifValue> values, IExifValue exif, object? value)
{ {
if (!exif.TrySetValue(value)) if (!exif.TrySetValue(value))
{ {
@ -510,7 +510,7 @@ internal abstract class BaseExifReader
private void AddInvalidTag(ExifTag tag) private void AddInvalidTag(ExifTag tag)
=> (this.invalidTags ??= new List<ExifTag>()).Add(tag); => (this.invalidTags ??= new List<ExifTag>()).Add(tag);
private void AddSubIfd(object val) private void AddSubIfd(object? val)
=> (this.subIfds ??= new List<ulong>()).Add(Convert.ToUInt64(val, CultureInfo.InvariantCulture)); => (this.subIfds ??= new List<ulong>()).Add(Convert.ToUInt64(val, CultureInfo.InvariantCulture));
private void Seek(ulong pos) private void Seek(ulong pos)

23
src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
#nullable disable
using System.Diagnostics.CodeAnalysis;
using System.Reflection; using System.Reflection;
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -26,29 +26,34 @@ internal sealed class ExifTagDescriptionAttribute : Attribute
/// </summary> /// </summary>
/// <param name="tag">The tag.</param> /// <param name="tag">The tag.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="description">The description.</param>
/// <returns> /// <returns>
/// The <see cref="string"/>. /// True when description was found
/// </returns> /// </returns>
public static string GetDescription(ExifTag tag, object value) public static bool TryGetDescription(ExifTag tag, object? value, [NotNullWhen(true)] out string? description)
{ {
var tagValue = (ExifTagValue)(ushort)tag; ExifTagValue tagValue = (ExifTagValue)(ushort)tag;
FieldInfo field = typeof(ExifTagValue).GetField(tagValue.ToString(), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); FieldInfo? field = typeof(ExifTagValue).GetField(tagValue.ToString(), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
description = null;
if (field is null) if (field is null)
{ {
return null; return false;
} }
foreach (CustomAttributeData customAttribute in field.CustomAttributes) foreach (CustomAttributeData customAttribute in field.CustomAttributes)
{ {
object attributeValue = customAttribute.ConstructorArguments[0].Value; object? attributeValue = customAttribute.ConstructorArguments[0].Value;
if (Equals(attributeValue, value)) if (Equals(attributeValue, value))
{ {
return (string)customAttribute.ConstructorArguments[1].Value; description = (string?)customAttribute.ConstructorArguments[1].Value;
return description is not null;
} }
} }
return null; return false;
} }
} }

37
src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers.Binary; using System.Buffers.Binary;
@ -16,7 +15,7 @@ internal sealed class ExifWriter
/// </summary> /// </summary>
private readonly ExifParts allowedParts; private readonly ExifParts allowedParts;
private readonly IList<IExifValue> values; private readonly IList<IExifValue> values;
private List<int> dataOffsets; private List<int>? dataOffsets;
private readonly List<IExifValue> ifdValues; private readonly List<IExifValue> ifdValues;
private readonly List<IExifValue> exifValues; private readonly List<IExifValue> exifValues;
private readonly List<IExifValue> gpsValues; private readonly List<IExifValue> gpsValues;
@ -45,8 +44,8 @@ internal sealed class ExifWriter
{ {
const uint startIndex = 0; const uint startIndex = 0;
IExifValue exifOffset = GetOffsetValue(this.ifdValues, this.exifValues, ExifTag.SubIFDOffset); IExifValue? exifOffset = GetOffsetValue(this.ifdValues, this.exifValues, ExifTag.SubIFDOffset);
IExifValue gpsOffset = GetOffsetValue(this.ifdValues, this.gpsValues, ExifTag.GPSIFDOffset); IExifValue? gpsOffset = GetOffsetValue(this.ifdValues, this.gpsValues, ExifTag.GPSIFDOffset);
uint ifdLength = GetLength(this.ifdValues); uint ifdLength = GetLength(this.ifdValues);
uint exifLength = GetLength(this.exifValues); uint exifLength = GetLength(this.exifValues);
@ -91,7 +90,7 @@ internal sealed class ExifWriter
if (gpsLength > 0) if (gpsLength > 0)
{ {
i = this.WriteHeaders(this.gpsValues, result, i); i = this.WriteHeaders(this.gpsValues, result, i);
i = this.WriteData(startIndex, this.gpsValues, result, i); this.WriteData(startIndex, this.gpsValues, result, i);
} }
return result; return result;
@ -160,7 +159,7 @@ internal sealed class ExifWriter
return offset + 4; return offset + 4;
} }
private static IExifValue GetOffsetValue(List<IExifValue> ifdValues, List<IExifValue> values, ExifTag offset) private static IExifValue? GetOffsetValue(List<IExifValue> ifdValues, List<IExifValue> values, ExifTag offset)
{ {
int index = -1; int index = -1;
@ -179,8 +178,12 @@ internal sealed class ExifWriter
return ifdValues[index]; return ifdValues[index];
} }
ExifValue result = ExifValues.Create(offset); ExifValue? result = ExifValues.Create(offset);
ifdValues.Add(result);
if (result is not null)
{
ifdValues.Add(result);
}
return result; return result;
} }
@ -219,15 +222,14 @@ internal sealed class ExifWriter
private static bool HasValue(IExifValue exifValue) private static bool HasValue(IExifValue exifValue)
{ {
object value = exifValue.GetValue(); object? value = exifValue.GetValue();
if (value is null) if (value is null)
{ {
return false; return false;
} }
if (exifValue.DataType == ExifDataType.Ascii) if (exifValue.DataType == ExifDataType.Ascii && value is string stringValue)
{ {
string stringValue = (string)value;
return stringValue.Length > 0; return stringValue.Length > 0;
} }
@ -270,11 +272,11 @@ internal sealed class ExifWriter
internal static uint GetNumberOfComponents(IExifValue exifValue) internal static uint GetNumberOfComponents(IExifValue exifValue)
{ {
object value = exifValue.GetValue(); object? value = exifValue.GetValue();
if (ExifUcs2StringHelpers.IsUcs2Tag((ExifTagValue)(ushort)exifValue.Tag)) if (ExifUcs2StringHelpers.IsUcs2Tag((ExifTagValue)(ushort)exifValue.Tag))
{ {
return (uint)ExifUcs2StringHelpers.Ucs2Encoding.GetByteCount((string)value); return (uint)ExifUcs2StringHelpers.Ucs2Encoding.GetByteCount((string?)value!);
} }
if (value is EncodedString encodedString) if (value is EncodedString encodedString)
@ -284,7 +286,7 @@ internal sealed class ExifWriter
if (exifValue.DataType == ExifDataType.Ascii) if (exifValue.DataType == ExifDataType.Ascii)
{ {
return (uint)ExifConstants.DefaultEncoding.GetByteCount((string)value) + 1; return (uint)ExifConstants.DefaultEncoding.GetByteCount((string?)value!) + 1;
} }
if (value is Array arrayValue) if (value is Array arrayValue)
@ -298,7 +300,7 @@ internal sealed class ExifWriter
private static int WriteArray(IExifValue value, Span<byte> destination, int offset) private static int WriteArray(IExifValue value, Span<byte> destination, int offset)
{ {
int newOffset = offset; int newOffset = offset;
foreach (object obj in (Array)value.GetValue()) foreach (object obj in (Array)value.GetValue()!)
{ {
newOffset = WriteValue(value.DataType, obj, destination, newOffset); newOffset = WriteValue(value.DataType, obj, destination, newOffset);
} }
@ -308,7 +310,7 @@ internal sealed class ExifWriter
private int WriteData(uint startIndex, List<IExifValue> values, Span<byte> destination, int offset) private int WriteData(uint startIndex, List<IExifValue> values, Span<byte> destination, int offset)
{ {
if (this.dataOffsets.Count == 0) if (this.dataOffsets is null || this.dataOffsets.Count == 0)
{ {
return offset; return offset;
} }
@ -428,7 +430,8 @@ internal sealed class ExifWriter
internal static int WriteValue(IExifValue exifValue, Span<byte> destination, int offset) internal static int WriteValue(IExifValue exifValue, Span<byte> destination, int offset)
{ {
object value = exifValue.GetValue(); object? value = exifValue.GetValue();
Guard.NotNull(value);
if (ExifUcs2StringHelpers.IsUcs2Tag((ExifTagValue)(ushort)exifValue.Tag)) if (ExifUcs2StringHelpers.IsUcs2Tag((ExifTagValue)(ushort)exifValue.Tag))
{ {

7
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifArrayValue{TValueType}.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
#nullable disable
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -23,11 +22,11 @@ internal abstract class ExifArrayValue<TValueType> : ExifValue, IExifValue<TValu
public override bool IsArray => true; public override bool IsArray => true;
public TValueType[] Value { get; set; } public TValueType[]? Value { get; set; }
public override object GetValue() => this.Value; public override object? GetValue() => this.Value;
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (value is null) if (value is null)
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByte.cs

@ -20,7 +20,7 @@ internal sealed class ExifByte : ExifValue<byte>
protected override string StringValue => this.Value.ToString("X2", CultureInfo.InvariantCulture); protected override string StringValue => this.Value.ToString("X2", CultureInfo.InvariantCulture);
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs

@ -16,7 +16,7 @@ internal sealed class ExifByteArray : ExifArrayValue<byte>
public override ExifDataType DataType { get; } public override ExifDataType DataType { get; }
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDouble.cs

@ -26,7 +26,7 @@ internal sealed class ExifDouble : ExifValue<double>
protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture);
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs

@ -24,7 +24,7 @@ internal sealed class ExifEncodedString : ExifValue<EncodedString>
protected override string StringValue => this.Value.Text; protected override string StringValue => this.Value.Text;
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloat.cs

@ -21,7 +21,7 @@ internal sealed class ExifFloat : ExifValue<float>
protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture);
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong.cs

@ -26,7 +26,7 @@ internal sealed class ExifLong : ExifValue<uint>
protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture);
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs

@ -26,7 +26,7 @@ internal sealed class ExifLong8 : ExifValue<ulong>
protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture);
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs

@ -34,7 +34,7 @@ internal sealed class ExifLong8Array : ExifArrayValue<ulong>
} }
} }
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs

@ -32,7 +32,7 @@ internal sealed class ExifNumber : ExifValue<Number>
protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture);
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs

@ -34,7 +34,7 @@ internal sealed class ExifNumberArray : ExifArrayValue<Number>
} }
} }
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRational.cs

@ -26,7 +26,7 @@ internal sealed class ExifRational : ExifValue<Rational>
protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture);
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRationalArray.cs

@ -22,7 +22,7 @@ internal sealed class ExifRationalArray : ExifArrayValue<Rational>
public override ExifDataType DataType => ExifDataType.Rational; public override ExifDataType DataType => ExifDataType.Rational;
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShort.cs

@ -26,7 +26,7 @@ internal sealed class ExifShort : ExifValue<ushort>
protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture);
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShortArray.cs

@ -22,7 +22,7 @@ internal sealed class ExifShortArray : ExifArrayValue<ushort>
public override ExifDataType DataType => ExifDataType.Short; public override ExifDataType DataType => ExifDataType.Short;
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByte.cs

@ -21,7 +21,7 @@ internal sealed class ExifSignedByte : ExifValue<sbyte>
protected override string StringValue => this.Value.ToString("X2", CultureInfo.InvariantCulture); protected override string StringValue => this.Value.ToString("X2", CultureInfo.InvariantCulture);
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShort.cs

@ -21,7 +21,7 @@ internal sealed class ExifSignedShort : ExifValue<short>
protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture);
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

2
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs

@ -17,7 +17,7 @@ internal sealed class ExifSignedShortArray : ExifArrayValue<short>
public override ExifDataType DataType => ExifDataType.SignedShort; public override ExifDataType DataType => ExifDataType.SignedShort;
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

4
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifString.cs

@ -24,9 +24,9 @@ internal sealed class ExifString : ExifValue<string>
public override ExifDataType DataType => ExifDataType.Ascii; public override ExifDataType DataType => ExifDataType.Ascii;
protected override string StringValue => this.Value; protected override string? StringValue => this.Value;
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

6
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifUcs2String.cs

@ -22,11 +22,11 @@ internal sealed class ExifUcs2String : ExifValue<string>
public override ExifDataType DataType => ExifDataType.Byte; public override ExifDataType DataType => ExifDataType.Byte;
protected override string StringValue => this.Value; protected override string? StringValue => this.Value;
public override object GetValue() => this.Value; public override object? GetValue() => this.Value;
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (base.TrySetValue(value)) if (base.TrySetValue(value))
{ {

11
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
#nullable disable
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -28,7 +27,7 @@ internal abstract class ExifValue : IExifValue, IEquatable<ExifTag>
else else
{ {
// All array types are value types so Clone() is sufficient here. // All array types are value types so Clone() is sufficient here.
var array = (Array)other.GetValue(); Array? array = (Array?)other.GetValue();
this.TrySetValue(array?.Clone()); this.TrySetValue(array?.Clone());
} }
} }
@ -43,7 +42,7 @@ internal abstract class ExifValue : IExifValue, IEquatable<ExifTag>
public static bool operator !=(ExifValue left, ExifTag right) => !Equals(left, right); public static bool operator !=(ExifValue left, ExifTag right) => !Equals(left, right);
public override bool Equals(object obj) public override bool Equals(object? obj)
{ {
if (obj is null) if (obj is null)
{ {
@ -69,14 +68,14 @@ internal abstract class ExifValue : IExifValue, IEquatable<ExifTag>
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public bool Equals(ExifTag other) => this.Tag.Equals(other); public bool Equals(ExifTag? other) => this.Tag.Equals(other);
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public override int GetHashCode() => HashCode.Combine(this.Tag, this.GetValue()); public override int GetHashCode() => HashCode.Combine(this.Tag, this.GetValue());
public abstract object GetValue(); public abstract object? GetValue();
public abstract bool TrySetValue(object value); public abstract bool TrySetValue(object? value);
public abstract IExifValue DeepClone(); public abstract IExifValue DeepClone();
} }

11
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs

@ -1,18 +1,17 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
#nullable disable
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Metadata.Profiles.Exif;
internal static partial class ExifValues internal static partial class ExifValues
{ {
public static ExifValue Create(ExifTagValue tag) => (ExifValue)CreateValue(tag); public static ExifValue? Create(ExifTagValue tag) => (ExifValue?)CreateValue(tag);
public static ExifValue Create(ExifTag tag) => (ExifValue)CreateValue((ExifTagValue)(ushort)tag); public static ExifValue? Create(ExifTag tag) => (ExifValue?)CreateValue((ExifTagValue)(ushort)tag);
public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, ulong numberOfComponents) => Create(tag, dataType, numberOfComponents != 1); public static ExifValue? Create(ExifTagValue tag, ExifDataType dataType, ulong numberOfComponents) => Create(tag, dataType, numberOfComponents != 1);
public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, bool isArray) public static ExifValue? Create(ExifTagValue tag, ExifDataType dataType, bool isArray)
{ {
switch (dataType) switch (dataType)
{ {
@ -49,7 +48,7 @@ internal static partial class ExifValues
} }
} }
private static object CreateValue(ExifTagValue tag) private static object? CreateValue(ExifTagValue tag)
{ {
switch (tag) switch (tag)
{ {

20
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue{TValueType}.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
#nullable disable
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -21,16 +20,16 @@ internal abstract class ExifValue<TValueType> : ExifValue, IExifValue<TValueType
{ {
} }
public TValueType Value { get; set; } public TValueType? Value { get; set; }
/// <summary> /// <summary>
/// Gets the value of the current instance as a string. /// Gets the value of the current instance as a string.
/// </summary> /// </summary>
protected abstract string StringValue { get; } protected abstract string? StringValue { get; }
public override object GetValue() => this.Value; public override object? GetValue() => this.Value;
public override bool TrySetValue(object value) public override bool TrySetValue(object? value)
{ {
if (value is null) if (value is null)
{ {
@ -49,14 +48,5 @@ internal abstract class ExifValue<TValueType> : ExifValue, IExifValue<TValueType
return false; return false;
} }
public override string ToString() public override string? ToString() => ExifTagDescriptionAttribute.TryGetDescription(this.Tag, this.Value, out string? description) ? description : this.StringValue;
{
if (this.Value == null)
{
return null;
}
string description = ExifTagDescriptionAttribute.GetDescription(this.Tag, this.Value);
return description ?? this.StringValue;
}
} }

4
src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue.cs

@ -27,12 +27,12 @@ public interface IExifValue : IDeepCloneable<IExifValue>
/// Gets the value of this exif value. /// Gets the value of this exif value.
/// </summary> /// </summary>
/// <returns>The value of this exif value.</returns> /// <returns>The value of this exif value.</returns>
object GetValue(); object? GetValue();
/// <summary> /// <summary>
/// Sets the value of this exif value. /// Sets the value of this exif value.
/// </summary> /// </summary>
/// <param name="value">The value of this exif value.</param> /// <param name="value">The value of this exif value.</param>
/// <returns>A value indicating whether the value could be set.</returns> /// <returns>A value indicating whether the value could be set.</returns>
bool TrySetValue(object value); bool TrySetValue(object? value);
} }

2
src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue{TValueType}.cs

@ -12,5 +12,5 @@ public interface IExifValue<TValueType> : IExifValue
/// <summary> /// <summary>
/// Gets or sets the value. /// Gets or sets the value.
/// </summary> /// </summary>
TValueType Value { get; set; } TValueType? Value { get; set; }
} }

3
src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs

@ -88,8 +88,7 @@ internal class AutoOrientProcessor<TPixel> : ImageProcessor<TPixel>
return ExifOrientationMode.Unknown; return ExifOrientationMode.Unknown;
} }
IExifValue<ushort> value = source.Metadata.ExifProfile.GetValue(ExifTag.Orientation); if (!source.Metadata.ExifProfile.TryGetValue(ExifTag.Orientation, out IExifValue<ushort>? value))
if (value is null)
{ {
return ExifOrientationMode.Unknown; return ExifOrientationMode.Unknown;
} }

4
src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs

@ -26,12 +26,12 @@ internal static class TransformProcessorHelpers
} }
// Only set the value if it already exists. // Only set the value if it already exists.
if (profile.GetValue(ExifTag.PixelXDimension) != null) if (profile.TryGetValue(ExifTag.PixelXDimension, out _))
{ {
profile.SetValue(ExifTag.PixelXDimension, image.Width); profile.SetValue(ExifTag.PixelXDimension, image.Width);
} }
if (profile.GetValue(ExifTag.PixelYDimension) != null) if (profile.TryGetValue(ExifTag.PixelYDimension, out _))
{ {
profile.SetValue(ExifTag.PixelYDimension, image.Height); profile.SetValue(ExifTag.PixelYDimension, image.Height);
} }

1
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs

@ -8,6 +8,7 @@ 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;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.Jpg; namespace SixLabors.ImageSharp.Tests.Formats.Jpg;

1
tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs

@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Formats.Png;
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;
using SixLabors.ImageSharp.Tests.TestUtilities;
namespace SixLabors.ImageSharp.Tests.Formats.Png; namespace SixLabors.ImageSharp.Tests.Formats.Png;

1
tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs

@ -6,6 +6,7 @@ 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;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using static SixLabors.ImageSharp.Tests.TestImages.BigTiff; using static SixLabors.ImageSharp.Tests.TestImages.BigTiff;

1
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs

@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;

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

@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using static SixLabors.ImageSharp.Tests.TestImages.Tiff; using static SixLabors.ImageSharp.Tests.TestImages.Tiff;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff;
@ -167,9 +168,9 @@ public class TiffMetadataTests
Assert.Equal("Make", exifProfile.GetValue(ExifTag.Make).Value); Assert.Equal("Make", exifProfile.GetValue(ExifTag.Make).Value);
Assert.Equal("Model", exifProfile.GetValue(ExifTag.Model).Value); Assert.Equal("Model", exifProfile.GetValue(ExifTag.Model).Value);
Assert.Equal("ImageSharp", exifProfile.GetValue(ExifTag.Software).Value); Assert.Equal("ImageSharp", exifProfile.GetValue(ExifTag.Software).Value);
Assert.Null(exifProfile.GetValue(ExifTag.DateTime)?.Value); Assert.Null(exifProfile.GetValue(ExifTag.DateTime, false)?.Value);
Assert.Equal("Artist", exifProfile.GetValue(ExifTag.Artist).Value); Assert.Equal("Artist", exifProfile.GetValue(ExifTag.Artist).Value);
Assert.Null(exifProfile.GetValue(ExifTag.HostComputer)?.Value); Assert.Null(exifProfile.GetValue(ExifTag.HostComputer, false)?.Value);
Assert.Equal("Copyright", exifProfile.GetValue(ExifTag.Copyright).Value); Assert.Equal("Copyright", exifProfile.GetValue(ExifTag.Copyright).Value);
Assert.Equal(4, exifProfile.GetValue(ExifTag.Rating).Value); Assert.Equal(4, exifProfile.GetValue(ExifTag.Rating).Value);
Assert.Equal(75, exifProfile.GetValue(ExifTag.RatingPercent).Value); Assert.Equal(75, exifProfile.GetValue(ExifTag.RatingPercent).Value);
@ -178,9 +179,9 @@ public class TiffMetadataTests
Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.YResolution).Value); Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.YResolution).Value);
Assert.Equal(new Number[] { 8u }, exifProfile.GetValue(ExifTag.StripOffsets)?.Value, new NumberComparer()); Assert.Equal(new Number[] { 8u }, exifProfile.GetValue(ExifTag.StripOffsets)?.Value, new NumberComparer());
Assert.Equal(new Number[] { 285u }, exifProfile.GetValue(ExifTag.StripByteCounts)?.Value, new NumberComparer()); Assert.Equal(new Number[] { 285u }, exifProfile.GetValue(ExifTag.StripByteCounts)?.Value, new NumberComparer());
Assert.Null(exifProfile.GetValue(ExifTag.ExtraSamples)?.Value); Assert.Null(exifProfile.GetValue(ExifTag.ExtraSamples, false)?.Value);
Assert.Equal(32u, exifProfile.GetValue(ExifTag.RowsPerStrip).Value); Assert.Equal(32u, exifProfile.GetValue(ExifTag.RowsPerStrip).Value);
Assert.Null(exifProfile.GetValue(ExifTag.SampleFormat)); Assert.Null(exifProfile.GetValue(ExifTag.SampleFormat, false));
Assert.Equal(PixelResolutionUnit.PixelsPerInch, UnitConverter.ExifProfileToResolutionUnit(exifProfile)); Assert.Equal(PixelResolutionUnit.PixelsPerInch, UnitConverter.ExifProfileToResolutionUnit(exifProfile));
ushort[] colorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; ushort[] colorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value;
Assert.NotNull(colorMap); Assert.NotNull(colorMap);

1
tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs

@ -5,6 +5,7 @@ using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.Webp; namespace SixLabors.ImageSharp.Tests.Formats.Webp;

1
tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs

@ -4,6 +4,7 @@
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;
using SixLabors.ImageSharp.Tests.TestUtilities;
namespace SixLabors.ImageSharp.Tests.Metadata; namespace SixLabors.ImageSharp.Tests.Metadata;

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

@ -7,6 +7,7 @@ using SixLabors.ImageSharp.Formats.Webp;
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;
using SixLabors.ImageSharp.Tests.TestUtilities;
namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif;
@ -255,7 +256,7 @@ public class ExifProfileTests
xResolution = image.Metadata.ExifProfile.GetValue(ExifTag.XResolution); xResolution = image.Metadata.ExifProfile.GetValue(ExifTag.XResolution);
Assert.Equal(new Rational(150.0), xResolution.Value); Assert.Equal(new Rational(150.0), xResolution.Value);
referenceBlackWhite = image.Metadata.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); referenceBlackWhite = image.Metadata.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite, false);
Assert.Null(referenceBlackWhite); Assert.Null(referenceBlackWhite);
latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude);
@ -300,7 +301,7 @@ public class ExifProfileTests
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, false));
Assert.Equal(profileCount - 1, image.Metadata.ExifProfile.Values.Count); Assert.Equal(profileCount - 1, image.Metadata.ExifProfile.Values.Count);
} }
@ -345,13 +346,14 @@ public class ExifProfileTests
ExifProfile profile = GetExifProfile(); ExifProfile profile = GetExifProfile();
TestProfile(profile); TestProfile(profile);
bool retVal = profile.TryCreateThumbnail(out Image thumbnail);
using Image thumbnail = profile.CreateThumbnail(); Assert.True(retVal);
Assert.NotNull(thumbnail); Assert.NotNull(thumbnail);
Assert.Equal(256, thumbnail.Width); Assert.Equal(256, thumbnail.Width);
Assert.Equal(170, thumbnail.Height); Assert.Equal(170, thumbnail.Height);
using Image<Rgba32> genericThumbnail = profile.CreateThumbnail<Rgba32>(); retVal = profile.TryCreateThumbnail<Rgba32>(out Image<Rgba32> genericThumbnail);
Assert.True(retVal);
Assert.NotNull(genericThumbnail); Assert.NotNull(genericThumbnail);
Assert.Equal(256, genericThumbnail.Width); Assert.Equal(256, genericThumbnail.Width);
Assert.Equal(170, genericThumbnail.Height); Assert.Equal(170, genericThumbnail.Height);

1
tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Tests.TestUtilities;
namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif;

4
tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs

@ -23,7 +23,9 @@ public class ExifValueTests
{ {
Assert.NotNull(this.profile); Assert.NotNull(this.profile);
return this.profile.GetValue(ExifTag.Software); this.profile.TryGetValue(ExifTag.Software, out IExifValue<string> value);
return value;
} }
[Fact] [Fact]

1
tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs

@ -4,6 +4,7 @@
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Processing.Processors.Transforms;
using SixLabors.ImageSharp.Tests.TestUtilities;
namespace SixLabors.ImageSharp.Tests.Processing.Transforms; namespace SixLabors.ImageSharp.Tests.Processing.Transforms;

18
tests/ImageSharp.Tests/TestUtilities/ExifProfileExtensions.cs

@ -0,0 +1,18 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Tests.TestUtilities;
public static class ExifProfileExtensions
{
public static IExifValue<T> GetValue<T>(this ExifProfile exifProfileInput, ExifTag<T> tag, bool assertTrue = true)
{
bool boolValue = exifProfileInput.TryGetValue(tag, out IExifValue<T> value);
Assert.Equal(assertTrue, boolValue);
return value;
}
}
Loading…
Cancel
Save