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)]
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 resolution is null ? DefaultResolutionUnit : (PixelResolutionUnit)(byte)(resolution.Value - 1);
return DefaultResolutionUnit;
}
/// <summary>

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

@ -717,9 +717,12 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
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>

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="sample">The tiff bits per sample.</param>
/// <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)
{

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)
where TPixel : unmanaged, IPixel<TPixel>
{
int rowsPerStrip = tags.GetValue(ExifTag.RowsPerStrip) != null
? (int)tags.GetValue(ExifTag.RowsPerStrip).Value
: TiffConstants.RowsPerStripInfinity;
int rowsPerStrip;
if (tags.TryGetValue(ExifTag.RowsPerStrip, out IExifValue<Number> value))
{
rowsPerStrip = (int)value.Value;
}
else
{
rowsPerStrip = TiffConstants.RowsPerStripInfinity;
}
Array stripOffsetsArray = (Array)tags.GetValueInternal(ExifTag.StripOffsets).GetValue();
Array stripByteCountsArray = (Array)tags.GetValueInternal(ExifTag.StripByteCounts).GetValue();
@ -320,8 +326,18 @@ internal class TiffDecoderCore : IImageDecoderInternals
int width = pixels.Width;
int height = pixels.Height;
int tileWidth = (int)tags.GetValue(ExifTag.TileWidth).Value;
int tileLength = (int)tags.GetValue(ExifTag.TileLength).Value;
if (!tags.TryGetValue(ExifTag.TileWidth, out IExifValue<Number> valueWidth))
{
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 tilesDown = (height + tileLength - 1) / tileLength;
@ -775,10 +791,9 @@ internal class TiffDecoderCore : IImageDecoderInternals
/// <returns>The image width.</returns>
private static int GetImageWidth(ExifProfile exifProfile)
{
IExifValue<Number> width = exifProfile.GetValue(ExifTag.ImageWidth);
if (width == null)
if (!exifProfile.TryGetValue(ExifTag.ImageWidth, out IExifValue<Number> width))
{
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));
@ -793,8 +808,7 @@ internal class TiffDecoderCore : IImageDecoderInternals
/// <returns>The image height.</returns>
private static int GetImageHeight(ExifProfile exifProfile)
{
IExifValue<Number> height = exifProfile.GetValue(ExifTag.ImageLength);
if (height == null)
if (!exifProfile.TryGetValue(ExifTag.ImageLength, out IExifValue<Number> height))
{
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);
}
IExifValue<byte[]> xmpProfileBytes = frameMetaData.ExifProfile.GetValue(ExifTag.XMP);
if (xmpProfileBytes != null)
if (frameMetaData.ExifProfile.TryGetValue(ExifTag.XMP, out IExifValue<byte[]> xmpProfileBytes))
{
frameMetaData.XmpProfile = new XmpProfile(xmpProfileBytes.Value);
}
IExifValue<byte[]> iccProfileBytes = frameMetaData.ExifProfile.GetValue(ExifTag.IccProfile);
if (iccProfileBytes != null)
if (frameMetaData.ExifProfile.TryGetValue(ExifTag.IccProfile, out IExifValue<byte[]> iccProfileBytes))
{
frameMetaData.IccProfile = new IccProfile(iccProfileBytes.Value);
}
@ -70,16 +68,20 @@ internal static class TiffDecoderMetadataCreator
private static void SetResolution(ImageMetadata imageMetaData, ExifProfile exifProfile)
{
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 (verticalResolution != null)
if (exifProfile.TryGetValue(ExifTag.YResolution, out IExifValue<Rational> verticalResolution))
{
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)
{
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.");
}
TiffSampleFormat[] sampleFormats = exifProfile.GetValue(ExifTag.SampleFormat)?.Value?.Select(a => (TiffSampleFormat)a).ToArray();
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];
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)
{
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.");
}
if (exifProfile.GetValue(ExifTag.StripRowCounts)?.Value != null)
if (exifProfile.TryGetValue(ExifTag.StripRowCounts, out _))
{
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.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb;
options.SampleFormat = sampleFormat ?? TiffSampleFormat.UnsignedInteger;
options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24;
options.BitsPerSample = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0);
options.ReferenceBlackAndWhite = exifProfile.GetValue(ExifTag.ReferenceBlackWhite)?.Value;
options.YcbcrCoefficients = exifProfile.GetValue(ExifTag.YCbCrCoefficients)?.Value;
options.YcbcrSubSampling = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value;
if (exifProfile.TryGetValue(ExifTag.ReferenceBlackWhite, out IExifValue<Rational[]> blackWhiteValue))
{
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.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.ParseCompression(frameMetadata.Compression, exifProfile);
@ -394,9 +437,9 @@ internal static class TiffDecoderOptionsParser
case TiffPhotometricInterpretation.PaletteColor:
{
options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value;
if (options.ColorMap != null)
if (exifProfile.TryGetValue(ExifTag.ColorMap, out IExifValue<ushort[]> value))
{
options.ColorMap = value.Value;
if (options.BitsPerSample.Channels != 1)
{
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:
{
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)
{
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:
{
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;
}
@ -516,7 +571,14 @@ internal static class TiffDecoderOptionsParser
case TiffCompression.CcittGroup4Fax:
{
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;
}

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

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

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

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -78,15 +77,30 @@ public class TiffFrameMetadata : IDeepCloneable
{
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.Compression = (TiffCompression?)profile.GetValue(ExifTag.Compression)?.Value;
meta.PhotometricInterpretation = (TiffPhotometricInterpretation?)profile.GetValue(ExifTag.PhotometricInterpretation)?.Value;
meta.Predictor = (TiffPredictor?)profile.GetValue(ExifTag.Predictor)?.Value;
if (profile.TryGetValue(ExifTag.Compression, out IExifValue<ushort>? compressionValue))
{
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.Compression);

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

@ -1,21 +1,33 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Formats.Tiff;
internal static class TiffThrowHelper
{
[DoesNotReturn]
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}");
[DoesNotReturn]
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}");
[DoesNotReturn]
public static Exception ThrowInvalidHeader() => throw new ImageFormatException("Invalid TIFF file header.");
[DoesNotReturn]
public static void ThrowNotSupported(string message) => throw new NotSupportedException(message);
[DoesNotReturn]
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.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers.Binary;
using System.Text;
@ -27,7 +26,14 @@ internal static class ExifEncodedStringHelpers
// 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
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
{

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

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

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

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
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)
{
// TODO: We never call this constructor passing a non-null allocator.
}
/// <summary>
@ -90,13 +90,13 @@ internal abstract class BaseExifReader
private readonly byte[] buf4 = new byte[4];
private readonly byte[] buf2 = new byte[2];
private readonly MemoryAllocator allocator;
private readonly MemoryAllocator? allocator;
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.allocator = allocator;
@ -219,7 +219,7 @@ internal abstract class BaseExifReader
this.Seek(tag.Offset);
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);
}
}
@ -255,7 +255,7 @@ internal abstract class BaseExifReader
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)
{
@ -390,7 +390,7 @@ internal abstract class BaseExifReader
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)
{
@ -414,7 +414,7 @@ internal abstract class BaseExifReader
}
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);
}
}
@ -443,7 +443,7 @@ internal abstract class BaseExifReader
numberOfComponents = 8 / ExifDataTypes.GetSize(dataType);
}
ExifValue exifValue = tag switch
ExifValue? exifValue = tag switch
{
ExifTagValue.StripOffsets => new ExifLong8Array(ExifTagValue.StripOffsets),
ExifTagValue.StripByteCounts => new ExifLong8Array(ExifTagValue.StripByteCounts),
@ -471,12 +471,12 @@ internal abstract class BaseExifReader
}
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);
}
}
private void Add(IList<IExifValue> values, IExifValue exif, object value)
private void Add(IList<IExifValue> values, IExifValue exif, object? value)
{
if (!exif.TrySetValue(value))
{
@ -510,7 +510,7 @@ internal abstract class BaseExifReader
private void AddInvalidTag(ExifTag 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));
private void Seek(ulong pos)

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

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -26,29 +26,34 @@ internal sealed class ExifTagDescriptionAttribute : Attribute
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="value">The value.</param>
/// <param name="description">The description.</param>
/// <returns>
/// The <see cref="string"/>.
/// True when description was found
/// </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;
FieldInfo field = typeof(ExifTagValue).GetField(tagValue.ToString(), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
ExifTagValue tagValue = (ExifTagValue)(ushort)tag;
FieldInfo? field = typeof(ExifTagValue).GetField(tagValue.ToString(), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
description = null;
if (field is null)
{
return null;
return false;
}
foreach (CustomAttributeData customAttribute in field.CustomAttributes)
{
object attributeValue = customAttribute.ConstructorArguments[0].Value;
object? attributeValue = customAttribute.ConstructorArguments[0].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.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers.Binary;
@ -16,7 +15,7 @@ internal sealed class ExifWriter
/// </summary>
private readonly ExifParts allowedParts;
private readonly IList<IExifValue> values;
private List<int> dataOffsets;
private List<int>? dataOffsets;
private readonly List<IExifValue> ifdValues;
private readonly List<IExifValue> exifValues;
private readonly List<IExifValue> gpsValues;
@ -45,8 +44,8 @@ internal sealed class ExifWriter
{
const uint startIndex = 0;
IExifValue exifOffset = GetOffsetValue(this.ifdValues, this.exifValues, ExifTag.SubIFDOffset);
IExifValue gpsOffset = GetOffsetValue(this.ifdValues, this.gpsValues, ExifTag.GPSIFDOffset);
IExifValue? exifOffset = GetOffsetValue(this.ifdValues, this.exifValues, ExifTag.SubIFDOffset);
IExifValue? gpsOffset = GetOffsetValue(this.ifdValues, this.gpsValues, ExifTag.GPSIFDOffset);
uint ifdLength = GetLength(this.ifdValues);
uint exifLength = GetLength(this.exifValues);
@ -91,7 +90,7 @@ internal sealed class ExifWriter
if (gpsLength > 0)
{
i = this.WriteHeaders(this.gpsValues, result, i);
i = this.WriteData(startIndex, this.gpsValues, result, i);
this.WriteData(startIndex, this.gpsValues, result, i);
}
return result;
@ -160,7 +159,7 @@ internal sealed class ExifWriter
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;
@ -179,8 +178,12 @@ internal sealed class ExifWriter
return ifdValues[index];
}
ExifValue result = ExifValues.Create(offset);
ifdValues.Add(result);
ExifValue? result = ExifValues.Create(offset);
if (result is not null)
{
ifdValues.Add(result);
}
return result;
}
@ -219,15 +222,14 @@ internal sealed class ExifWriter
private static bool HasValue(IExifValue exifValue)
{
object value = exifValue.GetValue();
object? value = exifValue.GetValue();
if (value is null)
{
return false;
}
if (exifValue.DataType == ExifDataType.Ascii)
if (exifValue.DataType == ExifDataType.Ascii && value is string stringValue)
{
string stringValue = (string)value;
return stringValue.Length > 0;
}
@ -270,11 +272,11 @@ internal sealed class ExifWriter
internal static uint GetNumberOfComponents(IExifValue exifValue)
{
object value = exifValue.GetValue();
object? value = exifValue.GetValue();
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)
@ -284,7 +286,7 @@ internal sealed class ExifWriter
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)
@ -298,7 +300,7 @@ internal sealed class ExifWriter
private static int WriteArray(IExifValue value, Span<byte> destination, int 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);
}
@ -308,7 +310,7 @@ internal sealed class ExifWriter
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;
}
@ -428,7 +430,8 @@ internal sealed class ExifWriter
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))
{

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

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -23,11 +22,11 @@ internal abstract class ExifArrayValue<TValueType> : ExifValue, IExifValue<TValu
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)
{

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);
public override bool TrySetValue(object value)
public override bool TrySetValue(object? 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 bool TrySetValue(object value)
public override bool TrySetValue(object? 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);
public override bool TrySetValue(object value)
public override bool TrySetValue(object? 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;
public override bool TrySetValue(object value)
public override bool TrySetValue(object? 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);
public override bool TrySetValue(object value)
public override bool TrySetValue(object? 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);
public override bool TrySetValue(object value)
public override bool TrySetValue(object? 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);
public override bool TrySetValue(object value)
public override bool TrySetValue(object? 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))
{

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);
public override bool TrySetValue(object value)
public override bool TrySetValue(object? 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))
{

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);
public override bool TrySetValue(object value)
public override bool TrySetValue(object? 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 bool TrySetValue(object value)
public override bool TrySetValue(object? 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);
public override bool TrySetValue(object value)
public override bool TrySetValue(object? 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 bool TrySetValue(object value)
public override bool TrySetValue(object? 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);
public override bool TrySetValue(object value)
public override bool TrySetValue(object? 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);
public override bool TrySetValue(object value)
public override bool TrySetValue(object? 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 bool TrySetValue(object value)
public override bool TrySetValue(object? 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;
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))
{

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;
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))
{

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

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Runtime.CompilerServices;
@ -28,7 +27,7 @@ internal abstract class ExifValue : IExifValue, IEquatable<ExifTag>
else
{
// 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());
}
}
@ -43,7 +42,7 @@ internal abstract class ExifValue : IExifValue, IEquatable<ExifTag>
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)
{
@ -69,14 +68,14 @@ internal abstract class ExifValue : IExifValue, IEquatable<ExifTag>
}
[MethodImpl(InliningOptions.ShortMethod)]
public bool Equals(ExifTag other) => this.Tag.Equals(other);
public bool Equals(ExifTag? other) => this.Tag.Equals(other);
[MethodImpl(InliningOptions.ShortMethod)]
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();
}

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

@ -1,18 +1,17 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif;
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)
{
@ -49,7 +48,7 @@ internal static partial class ExifValues
}
}
private static object CreateValue(ExifTagValue tag)
private static object? CreateValue(ExifTagValue tag)
{
switch (tag)
{

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

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
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>
/// Gets the value of the current instance as a string.
/// </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)
{
@ -49,14 +48,5 @@ internal abstract class ExifValue<TValueType> : ExifValue, IExifValue<TValueType
return false;
}
public override string ToString()
{
if (this.Value == null)
{
return null;
}
string description = ExifTagDescriptionAttribute.GetDescription(this.Tag, this.Value);
return description ?? this.StringValue;
}
public override string? ToString() => ExifTagDescriptionAttribute.TryGetDescription(this.Tag, this.Value, out string? description) ? 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.
/// </summary>
/// <returns>The value of this exif value.</returns>
object GetValue();
object? GetValue();
/// <summary>
/// Sets the value of this exif value.
/// </summary>
/// <param name="value">The value of this exif value.</param>
/// <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>
/// Gets or sets the value.
/// </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;
}
IExifValue<ushort> value = source.Metadata.ExifProfile.GetValue(ExifTag.Orientation);
if (value is null)
if (!source.Metadata.ExifProfile.TryGetValue(ExifTag.Orientation, out IExifValue<ushort>? value))
{
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.
if (profile.GetValue(ExifTag.PixelXDimension) != null)
if (profile.TryGetValue(ExifTag.PixelXDimension, out _))
{
profile.SetValue(ExifTag.PixelXDimension, image.Width);
}
if (profile.GetValue(ExifTag.PixelYDimension) != null)
if (profile.TryGetValue(ExifTag.PixelYDimension, out _))
{
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.Icc;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
// ReSharper disable InconsistentNaming
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.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
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.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
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.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
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.Xmp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using static SixLabors.ImageSharp.Tests.TestImages.Tiff;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff;
@ -167,9 +168,9 @@ public class TiffMetadataTests
Assert.Equal("Make", exifProfile.GetValue(ExifTag.Make).Value);
Assert.Equal("Model", exifProfile.GetValue(ExifTag.Model).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.Null(exifProfile.GetValue(ExifTag.HostComputer)?.Value);
Assert.Null(exifProfile.GetValue(ExifTag.HostComputer, false)?.Value);
Assert.Equal("Copyright", exifProfile.GetValue(ExifTag.Copyright).Value);
Assert.Equal(4, exifProfile.GetValue(ExifTag.Rating).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(new Number[] { 8u }, exifProfile.GetValue(ExifTag.StripOffsets)?.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.Null(exifProfile.GetValue(ExifTag.SampleFormat));
Assert.Null(exifProfile.GetValue(ExifTag.SampleFormat, false));
Assert.Equal(PixelResolutionUnit.PixelsPerInch, UnitConverter.ExifProfileToResolutionUnit(exifProfile));
ushort[] colorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value;
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.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
// ReSharper disable InconsistentNaming
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.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
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.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif;
@ -255,7 +256,7 @@ public class ExifProfileTests
xResolution = image.Metadata.ExifProfile.GetValue(ExifTag.XResolution);
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);
latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude);
@ -300,7 +301,7 @@ public class ExifProfileTests
Assert.NotNull(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace));
Assert.True(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace));
Assert.False(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace));
Assert.Null(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace));
Assert.Null(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace, false));
Assert.Equal(profileCount - 1, image.Metadata.ExifProfile.Values.Count);
}
@ -345,13 +346,14 @@ public class ExifProfileTests
ExifProfile profile = GetExifProfile();
TestProfile(profile);
using Image thumbnail = profile.CreateThumbnail();
bool retVal = profile.TryCreateThumbnail(out Image thumbnail);
Assert.True(retVal);
Assert.NotNull(thumbnail);
Assert.Equal(256, thumbnail.Width);
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.Equal(256, genericThumbnail.Width);
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.
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Tests.TestUtilities;
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);
return this.profile.GetValue(ExifTag.Software);
this.profile.TryGetValue(ExifTag.Software, out IExifValue<string> value);
return value;
}
[Fact]

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

@ -4,6 +4,7 @@
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
using SixLabors.ImageSharp.Tests.TestUtilities;
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