diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 6d31e8c532..9942a0ca28 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -109,12 +109,14 @@ jobs: key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} restore-keys: ${{ runner.os }}-nuget- - - name: DotNet Setup Preview - if: ${{ matrix.options.sdk-preview == true }} + - name: DotNet Setup uses: actions/setup-dotnet@v1 with: - dotnet-version: ${{ matrix.options.sdk }} - include-prerelease: true + dotnet-version: | + 6.0.x + 5.0.x + 3.1.x + 2.1.x - name: DotNet Build if: ${{ matrix.options.sdk-preview != true }} diff --git a/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs b/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs new file mode 100644 index 0000000000..d83e737f12 --- /dev/null +++ b/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs @@ -0,0 +1,83 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Threading; + +namespace SixLabors.ImageSharp.Diagnostics +{ + /// + /// Represents the method to handle . + /// + public delegate void UndisposedAllocationDelegate(string allocationStackTrace); + + /// + /// Utilities to track memory usage and detect memory leaks from not disposing ImageSharp objects. + /// + public static class MemoryDiagnostics + { + private static int totalUndisposedAllocationCount; + + private static UndisposedAllocationDelegate undisposedAllocation; + private static int undisposedAllocationSubscriptionCounter; + private static readonly object SyncRoot = new(); + + /// + /// Fires when an ImageSharp object's undisposed memory resource leaks to the finalizer. + /// The event brings significant overhead, and is intended to be used for troubleshooting only. + /// For production diagnostics, use . + /// + public static event UndisposedAllocationDelegate UndisposedAllocation + { + add + { + lock (SyncRoot) + { + undisposedAllocationSubscriptionCounter++; + undisposedAllocation += value; + } + } + + remove + { + lock (SyncRoot) + { + undisposedAllocation -= value; + undisposedAllocationSubscriptionCounter--; + } + } + } + + /// + /// Gets a value indicating the total number of memory resource objects leaked to the finalizer. + /// + public static int TotalUndisposedAllocationCount => totalUndisposedAllocationCount; + + internal static bool UndisposedAllocationSubscribed => Volatile.Read(ref undisposedAllocationSubscriptionCounter) > 0; + + internal static void IncrementTotalUndisposedAllocationCount() => + Interlocked.Increment(ref totalUndisposedAllocationCount); + + internal static void DecrementTotalUndisposedAllocationCount() => + Interlocked.Decrement(ref totalUndisposedAllocationCount); + + internal static void RaiseUndisposedMemoryResource(string allocationStackTrace) + { + if (undisposedAllocation is null) + { + return; + } + + // Schedule on the ThreadPool, to avoid user callback messing up the finalizer thread. +#if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER + ThreadPool.QueueUserWorkItem( + stackTrace => undisposedAllocation?.Invoke(stackTrace), + allocationStackTrace, + preferLocal: false); +#else + ThreadPool.QueueUserWorkItem( + stackTrace => undisposedAllocation?.Invoke((string)stackTrace), + allocationStackTrace); +#endif + } + } +} diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 3c0bf9edf3..b17ae7f17a 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -376,7 +376,17 @@ namespace SixLabors.ImageSharp.Formats.Gif indices = this.Configuration.MemoryAllocator.Allocate2D(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean); this.ReadFrameIndices(indices); - ReadOnlySpan colorTable = MemoryMarshal.Cast((localColorTable ?? this.globalColorTable).GetSpan()); + Span rawColorTable = default; + if (localColorTable != null) + { + rawColorTable = localColorTable.GetSpan(); + } + else if (this.globalColorTable != null) + { + rawColorTable = this.globalColorTable.GetSpan(); + } + + ReadOnlySpan colorTable = MemoryMarshal.Cast(rawColorTable); this.ReadFrameColors(ref image, ref previousFrame, indices, colorTable, this.imageDescriptor); // Skip any remaining blocks @@ -415,6 +425,7 @@ namespace SixLabors.ImageSharp.Formats.Gif { int imageWidth = this.logicalScreenDescriptor.Width; int imageHeight = this.logicalScreenDescriptor.Height; + bool transFlag = this.graphicsControlExtension.TransparencyFlag; ImageFrame prevFrame = null; ImageFrame currentFrame = null; @@ -422,8 +433,15 @@ namespace SixLabors.ImageSharp.Formats.Gif if (previousFrame is null) { - // This initializes the image to become fully transparent because the alpha channel is zero. - image = new Image(this.Configuration, imageWidth, imageHeight, this.metadata); + if (!transFlag) + { + image = new Image(this.Configuration, imageWidth, imageHeight, Color.Black.ToPixel(), this.metadata); + } + else + { + // This initializes the image to become fully transparent because the alpha channel is zero. + image = new Image(this.Configuration, imageWidth, imageHeight, this.metadata); + } this.SetFrameMetadata(image.Frames.RootFrame.Metadata); @@ -445,6 +463,11 @@ namespace SixLabors.ImageSharp.Formats.Gif this.RestoreToBackground(imageFrame); } + if (colorTable.Length == 0) + { + return; + } + int interlacePass = 0; // The interlace pass int interlaceIncrement = 8; // The interlacing line increment int interlaceY = 0; // The current interlaced line @@ -452,7 +475,6 @@ namespace SixLabors.ImageSharp.Formats.Gif int descriptorBottom = descriptorTop + descriptor.Height; int descriptorLeft = descriptor.Left; int descriptorRight = descriptorLeft + descriptor.Width; - bool transFlag = this.graphicsControlExtension.TransparencyFlag; byte transIndex = this.graphicsControlExtension.TransparencyIndex; int colorTableMaxIdx = colorTable.Length - 1; @@ -635,10 +657,13 @@ namespace SixLabors.ImageSharp.Formats.Gif int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; this.gifMetadata.GlobalColorTableLength = globalColorTableLength; - this.globalColorTable = this.MemoryAllocator.Allocate(globalColorTableLength, AllocationOptions.Clean); + if (globalColorTableLength > 0) + { + this.globalColorTable = this.MemoryAllocator.Allocate(globalColorTableLength, AllocationOptions.Clean); - // Read the global color table data from the stream - stream.Read(this.globalColorTable.GetSpan()); + // Read the global color table data from the stream + stream.Read(this.globalColorTable.GetSpan()); + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 2ae3ae86bc..cf2fd02908 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -148,11 +148,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private void ParseBaselineData() { - if (this.componentsCount == this.frame.ComponentCount) + if (this.componentsCount != 1) { this.ParseBaselineDataInterleaved(); this.spectralConverter.CommitConversion(); } + else if (this.frame.ComponentCount == 1) + { + this.ParseBaselineDataSingleComponent(); + this.spectralConverter.CommitConversion(); + } else { this.ParseBaselineDataNonInterleaved(); @@ -161,7 +166,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private void ParseBaselineDataInterleaved() { - // Interleaved int mcu = 0; int mcusPerColumn = this.frame.McusPerColumn; int mcusPerLine = this.frame.McusPerLine; @@ -198,7 +202,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { if (buffer.NoData) { - // It is very likely that some spectral data was decoded before we encountered EOI marker + // It is very likely that some spectral data was decoded before we've encountered 'end of scan' // so we need to decode what's left and return (or maybe throw?) this.spectralConverter.ConvertStrideBaseline(); return; @@ -221,9 +225,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.HandleRestart(); } - // convert from spectral to actual pixels via given converter + // Convert from spectral to actual pixels via given converter this.spectralConverter.ConvertStrideBaseline(); } + + // Stride conversion must be sealed for stride conversion approach + this.spectralConverter.CommitConversion(); } private void ParseBaselineDataNonInterleaved() @@ -261,6 +268,52 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } + private void ParseBaselineDataSingleComponent() + { + JpegComponent component = this.frame.Components[0]; + int mcuLines = this.frame.McusPerColumn; + int w = component.WidthInBlocks; + int h = component.SamplingFactors.Height; + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; + + ref HuffmanScanBuffer buffer = ref this.scanBuffer; + + for (int i = 0; i < mcuLines; i++) + { + this.cancellationToken.ThrowIfCancellationRequested(); + + // decode from binary to spectral + for (int j = 0; j < h; j++) + { + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (int k = 0; k < w; k++) + { + if (buffer.NoData) + { + // It is very likely that some spectral data was decoded before we've encountered 'end of scan' + // so we need to decode what's left and return (or maybe throw?) + this.spectralConverter.ConvertStrideBaseline(); + return; + } + + this.DecodeBlockBaseline( + component, + ref Unsafe.Add(ref blockRef, k), + ref dcHuffmanTable, + ref acHuffmanTable); + + this.HandleRestart(); + } + } + + // Convert from spectral to actual pixels via given converter + this.spectralConverter.ConvertStrideBaseline(); + } + } + private void CheckProgressiveData() { // Validate successive scan parameters. diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 1b3a8e27f6..39c85c4f22 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -10,7 +10,7 @@ Apache-2.0 https://github.com/SixLabors/ImageSharp/ $(RepositoryUrl) - Image Resize Crop Gif Jpg Jpeg Bitmap Pbm Png Tga NetCore + Image Resize Crop Gif Jpg Jpeg Bitmap Pbm Png Tga Tiff WebP NetCore A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET Debug;Release;Debug-InnerLoop;Release-InnerLoop @@ -49,6 +49,7 @@ + @@ -57,13 +58,6 @@ - - - - - - - True diff --git a/src/ImageSharp/Memory/Allocators/Internals/RefCountedLifetimeGuard.cs b/src/ImageSharp/Memory/Allocators/Internals/RefCountedLifetimeGuard.cs deleted file mode 100644 index 61682aa567..0000000000 --- a/src/ImageSharp/Memory/Allocators/Internals/RefCountedLifetimeGuard.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.InteropServices; -using System.Threading; - -namespace SixLabors.ImageSharp.Memory.Internals -{ - /// - /// Implements reference counting lifetime guard mechanism similar to the one provided by , - /// but without the restriction of the guarded object being a handle. - /// - internal abstract class RefCountedLifetimeGuard : IDisposable - { - private int refCount = 1; - private int disposed; - private int released; - - ~RefCountedLifetimeGuard() - { - Interlocked.Exchange(ref this.disposed, 1); - this.ReleaseRef(); - } - - public bool IsDisposed => this.disposed == 1; - - public void AddRef() => Interlocked.Increment(ref this.refCount); - - public void ReleaseRef() - { - Interlocked.Decrement(ref this.refCount); - if (this.refCount == 0) - { - int wasReleased = Interlocked.Exchange(ref this.released, 1); - - if (wasReleased == 0) - { - this.Release(); - } - } - } - - public void Dispose() - { - int wasDisposed = Interlocked.Exchange(ref this.disposed, 1); - if (wasDisposed == 0) - { - this.ReleaseRef(); - GC.SuppressFinalize(this); - } - } - - protected abstract void Release(); - } -} diff --git a/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs b/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs new file mode 100644 index 0000000000..1c7d6cdfa9 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.InteropServices; +using System.Threading; +using SixLabors.ImageSharp.Diagnostics; + +namespace SixLabors.ImageSharp.Memory.Internals +{ + /// + /// Implements reference counting lifetime guard mechanism for memory resources + /// and maintains the value of . + /// + internal abstract class RefCountedMemoryLifetimeGuard : IDisposable + { + private int refCount = 1; + private int disposed; + private int released; + private string allocationStackTrace; + + protected RefCountedMemoryLifetimeGuard() + { + if (MemoryDiagnostics.UndisposedAllocationSubscribed) + { + this.allocationStackTrace = Environment.StackTrace; + } + + MemoryDiagnostics.IncrementTotalUndisposedAllocationCount(); + } + + ~RefCountedMemoryLifetimeGuard() + { + Interlocked.Exchange(ref this.disposed, 1); + this.ReleaseRef(true); + } + + public bool IsDisposed => this.disposed == 1; + + public void AddRef() => Interlocked.Increment(ref this.refCount); + + public void ReleaseRef() => this.ReleaseRef(false); + + public void Dispose() + { + int wasDisposed = Interlocked.Exchange(ref this.disposed, 1); + if (wasDisposed == 0) + { + this.ReleaseRef(); + GC.SuppressFinalize(this); + } + } + + protected abstract void Release(); + + private void ReleaseRef(bool finalizing) + { + Interlocked.Decrement(ref this.refCount); + if (this.refCount == 0) + { + int wasReleased = Interlocked.Exchange(ref this.released, 1); + + if (wasReleased == 0) + { + if (!finalizing) + { + MemoryDiagnostics.DecrementTotalUndisposedAllocationCount(); + } + else if (this.allocationStackTrace != null) + { + MemoryDiagnostics.RaiseUndisposedMemoryResource(this.allocationStackTrace); + } + + this.Release(); + } + } + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs index 9302c67e7d..2ea76da957 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Memory.Internals } } - private sealed class LifetimeGuard : RefCountedLifetimeGuard + private sealed class LifetimeGuard : RefCountedMemoryLifetimeGuard { private byte[] array; diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs index 666b248552..151fef69c4 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs @@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp.Memory.Internals return buffer; } - public RefCountedLifetimeGuard CreateGroupLifetimeGuard(UnmanagedMemoryHandle[] handles) => new GroupLifetimeGuard(this, handles); + public RefCountedMemoryLifetimeGuard CreateGroupLifetimeGuard(UnmanagedMemoryHandle[] handles) => new GroupLifetimeGuard(this, handles); - private sealed class GroupLifetimeGuard : RefCountedLifetimeGuard + private sealed class GroupLifetimeGuard : RefCountedMemoryLifetimeGuard { private readonly UniformUnmanagedMemoryPool pool; private readonly UnmanagedMemoryHandle[] handles; diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs index 5f0759f203..8221120240 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Memory.Internals /// /// Defines a strategy for managing unmanaged memory ownership. /// - internal abstract class UnmanagedBufferLifetimeGuard : RefCountedLifetimeGuard + internal abstract class UnmanagedBufferLifetimeGuard : RefCountedMemoryLifetimeGuard { private UnmanagedMemoryHandle handle; diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index 21faf8e562..01aac3148e 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Memory public sealed class Owned : MemoryGroup, IEnumerable> { private IMemoryOwner[] memoryOwners; - private RefCountedLifetimeGuard groupLifetimeGuard; + private RefCountedMemoryLifetimeGuard groupLifetimeGuard; public Owned(IMemoryOwner[] memoryOwners, int bufferLength, long totalLength, bool swappable) : base(bufferLength, totalLength) diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs index 39e1cef8a0..89592f776c 100644 --- a/src/ImageSharp/Metadata/ImageMetadata.cs +++ b/src/ImageSharp/Metadata/ImageMetadata.cs @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Metadata /// /// Gets or sets the resolution of the image in x- direction. - /// It is defined as the number of dots per inch and should be an positive value. + /// It is defined as the number of dots per and should be an positive value. /// /// The density of the image in x- direction. public double HorizontalResolution @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Metadata /// /// Gets or sets the resolution of the image in y- direction. - /// It is defined as the number of dots per inch and should be an positive value. + /// It is defined as the number of dots per and should be an positive value. /// /// The density of the image in y- direction. public double VerticalResolution @@ -108,10 +108,28 @@ namespace SixLabors.ImageSharp.Metadata /// /// Gets or sets unit of measure used when reporting resolution. - /// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity - /// 01 : Pixels per inch (2.54 cm) - /// 02 : Pixels per centimeter - /// 03 : Pixels per meter + /// + /// + /// Value + /// Unit + /// + /// + /// AspectRatio (00) + /// No units; width:height pixel aspect ratio = Ydensity:Xdensity + /// + /// + /// PixelsPerInch (01) + /// Pixels per inch (2.54 cm) + /// + /// + /// PixelsPerCentimeter (02) + /// Pixels per centimeter + /// + /// + /// PixelsPerMeter (03) + /// Pixels per meter (100 cm) + /// + /// /// public PixelResolutionUnit ResolutionUnits { get; set; } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/DC-X008-Translation-2019-E.pdf b/src/ImageSharp/Metadata/Profiles/Exif/DC-X008-Translation-2019-E.pdf index cd7141fc8d..22a1058168 100644 Binary files a/src/ImageSharp/Metadata/Profiles/Exif/DC-X008-Translation-2019-E.pdf and b/src/ImageSharp/Metadata/Profiles/Exif/DC-X008-Translation-2019-E.pdf differ diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs index 0c81f14dd4..543e3d5c4d 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Text; namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { @@ -22,5 +23,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif 0x00, 0x2A }; + + // UTF-8 is better than ASCII, UTF-8 encodes the ASCII codes the same way + public static Encoding DefaultEncoding => Encoding.UTF8; } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs new file mode 100644 index 0000000000..5fd613b1f0 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs @@ -0,0 +1,121 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Text; +using static SixLabors.ImageSharp.Metadata.Profiles.Exif.EncodedString; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal static class ExifEncodedStringHelpers + { + public const int CharacterCodeBytesLength = 8; + + private const ulong AsciiCode = 0x_00_00_00_49_49_43_53_41; + private const ulong JISCode = 0x_00_00_00_00_00_53_49_4A; + private const ulong UnicodeCode = 0x_45_44_4F_43_49_4E_55; + private const ulong UndefinedCode = 0x_00_00_00_00_00_00_00_00; + + private static ReadOnlySpan AsciiCodeBytes => new byte[] { 0x41, 0x53, 0x43, 0x49, 0x49, 0, 0, 0 }; + + private static ReadOnlySpan JISCodeBytes => new byte[] { 0x4A, 0x49, 0x53, 0, 0, 0, 0, 0 }; + + private static ReadOnlySpan UnicodeCodeBytes => new byte[] { 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0 }; + + private static ReadOnlySpan UndefinedCodeBytes => new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; + + // 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); + + public static bool IsEncodedString(ExifTagValue tag) => tag switch + { + ExifTagValue.UserComment or ExifTagValue.GPSProcessingMethod or ExifTagValue.GPSAreaInformation => true, + _ => false + }; + + public static ReadOnlySpan GetCodeBytes(CharacterCode code) => code switch + { + CharacterCode.ASCII => AsciiCodeBytes, + CharacterCode.JIS => JISCodeBytes, + CharacterCode.Unicode => UnicodeCodeBytes, + CharacterCode.Undefined => UndefinedCodeBytes, + _ => UndefinedCodeBytes + }; + + public static Encoding GetEncoding(CharacterCode code) => code switch + { + CharacterCode.ASCII => Encoding.ASCII, + CharacterCode.JIS => JIS0208Encoding, + CharacterCode.Unicode => Encoding.Unicode, + CharacterCode.Undefined => Encoding.UTF8, + _ => Encoding.UTF8 + }; + + public static bool TryParse(ReadOnlySpan buffer, out EncodedString encodedString) + { + if (TryDetect(buffer, out CharacterCode code)) + { + string text = GetEncoding(code).GetString(buffer.Slice(CharacterCodeBytesLength)); + encodedString = new EncodedString(code, text); + return true; + } + + encodedString = default; + return false; + } + + public static uint GetDataLength(EncodedString encodedString) => + (uint)GetEncoding(encodedString.Code).GetByteCount(encodedString.Text) + CharacterCodeBytesLength; + + public static int Write(EncodedString encodedString, Span destination) + { + GetCodeBytes(encodedString.Code).CopyTo(destination); + + string text = encodedString.Text; + int count = Write(GetEncoding(encodedString.Code), text, destination.Slice(CharacterCodeBytesLength)); + + return CharacterCodeBytesLength + count; + } + + public static unsafe int Write(Encoding encoding, string value, Span destination) + { + fixed (char* c = value) + { + fixed (byte* b = destination) + { + return encoding.GetBytes(c, value.Length, b, destination.Length); + } + } + } + + private static bool TryDetect(ReadOnlySpan buffer, out CharacterCode code) + { + if (buffer.Length >= CharacterCodeBytesLength) + { + ulong test = BinaryPrimitives.ReadUInt64LittleEndian(buffer); + switch (test) + { + case AsciiCode: + code = CharacterCode.ASCII; + return true; + case JISCode: + code = CharacterCode.JIS; + return true; + case UnicodeCode: + code = CharacterCode.Unicode; + return true; + case UndefinedCode: + code = CharacterCode.Undefined; + return true; + default: + break; + } + } + + code = default; + return false; + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 2fcd1cc07d..9b5e098c83 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -241,9 +241,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return result; } - private byte ConvertToByte(ReadOnlySpan buffer) => buffer[0]; - - private string ConvertToString(ReadOnlySpan buffer) + private static string ConvertToString(Encoding encoding, ReadOnlySpan buffer) { int nullCharIndex = buffer.IndexOf((byte)0); @@ -252,9 +250,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif buffer = buffer.Slice(0, nullCharIndex); } - return Encoding.UTF8.GetString(buffer); + return encoding.GetString(buffer); } + private byte ConvertToByte(ReadOnlySpan buffer) => buffer[0]; + private object ConvertValue(ExifDataType dataType, ReadOnlySpan buffer, bool isArray) { if (buffer.Length == 0) @@ -267,8 +267,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifDataType.Unknown: return null; case ExifDataType.Ascii: - return this.ConvertToString(buffer); + return ConvertToString(ExifConstants.DefaultEncoding, buffer); case ExifDataType.Byte: + case ExifDataType.Undefined: if (!isArray) { return this.ConvertToByte(buffer); @@ -354,13 +355,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } return ToArray(dataType, buffer, this.ConvertToUInt64); - case ExifDataType.Undefined: - if (!isArray) - { - return this.ConvertToByte(buffer); - } - return buffer.ToArray(); default: throw new NotSupportedException($"Data type {dataType} is not supported."); } @@ -453,7 +448,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif // Likewise, tags that point to other IFDs, like e.g. the SubIFDs tag, are now allowed to have the datatype TIFF_IFD8 in BigTIFF. // Again, the old datatypes TIFF_IFD, and the hardly recommendable TIFF_LONG, are still valid, too. // https://www.awaresystems.be/imaging/tiff/bigtiff.html - ExifValue exifValue = null; + ExifValue exifValue; switch (tag) { case ExifTagValue.StripOffsets: diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifUcs2StringHelpers.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifUcs2StringHelpers.cs new file mode 100644 index 0000000000..ccc1c80ade --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifUcs2StringHelpers.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Text; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal static class ExifUcs2StringHelpers + { + public static Encoding Ucs2Encoding => Encoding.GetEncoding("UCS-2"); + + public static bool IsUcs2Tag(ExifTagValue tag) => tag switch + { + ExifTagValue.XPAuthor or ExifTagValue.XPComment or ExifTagValue.XPKeywords or ExifTagValue.XPSubject or ExifTagValue.XPTitle => true, + _ => false, + }; + + public static int Write(string value, Span destination) => ExifEncodedStringHelpers.Write(Ucs2Encoding, value, destination); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs index e2ed569548..a14539bca2 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs @@ -4,7 +4,6 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; -using System.Text; namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { @@ -274,9 +273,19 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { object value = exifValue.GetValue(); + if (ExifUcs2StringHelpers.IsUcs2Tag((ExifTagValue)(ushort)exifValue.Tag)) + { + return (uint)ExifUcs2StringHelpers.Ucs2Encoding.GetByteCount((string)value); + } + + if (value is EncodedString encodedString) + { + return ExifEncodedStringHelpers.GetDataLength(encodedString); + } + if (exifValue.DataType == ExifDataType.Ascii) { - return (uint)Encoding.UTF8.GetBytes((string)value).Length + 1; + return (uint)ExifConstants.DefaultEncoding.GetByteCount((string)value) + 1; } if (value is Array arrayValue) @@ -289,11 +298,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif private static int WriteArray(IExifValue value, Span destination, int offset) { - if (value.DataType == ExifDataType.Ascii) - { - return WriteValue(ExifDataType.Ascii, value.GetValue(), destination, offset); - } - int newOffset = offset; foreach (object obj in (Array)value.GetValue()) { @@ -378,7 +382,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif switch (dataType) { case ExifDataType.Ascii: - offset = Write(Encoding.UTF8.GetBytes((string)value), destination, offset); + offset = Write(ExifConstants.DefaultEncoding.GetBytes((string)value), destination, offset); destination[offset] = 0; return offset + 1; case ExifDataType.Byte: @@ -425,14 +429,25 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } - internal static int WriteValue(IExifValue value, Span destination, int offset) + internal static int WriteValue(IExifValue exifValue, Span destination, int offset) { - if (value.IsArray && value.DataType != ExifDataType.Ascii) + object value = exifValue.GetValue(); + + if (ExifUcs2StringHelpers.IsUcs2Tag((ExifTagValue)(ushort)exifValue.Tag)) + { + return offset + ExifUcs2StringHelpers.Write((string)value, destination.Slice(offset)); + } + else if (value is EncodedString encodedString) + { + return offset + ExifEncodedStringHelpers.Write(encodedString, destination.Slice(offset)); + } + + if (exifValue.IsArray) { - return WriteArray(value, destination, offset); + return WriteArray(exifValue, destination, offset); } - return WriteValue(value.DataType, value.GetValue(), destination, offset); + return WriteValue(exifValue.DataType, value, destination, offset); } } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs index fdde66c513..964fb6e948 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs @@ -41,31 +41,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// public static ExifTag TIFFEPStandardID => new ExifTag(ExifTagValue.TIFFEPStandardID); - /// - /// Gets the XPTitle exif tag. - /// - public static ExifTag XPTitle => new ExifTag(ExifTagValue.XPTitle); - - /// - /// Gets the XPComment exif tag. - /// - public static ExifTag XPComment => new ExifTag(ExifTagValue.XPComment); - - /// - /// Gets the XPAuthor exif tag. - /// - public static ExifTag XPAuthor => new ExifTag(ExifTagValue.XPAuthor); - - /// - /// Gets the XPKeywords exif tag. - /// - public static ExifTag XPKeywords => new ExifTag(ExifTagValue.XPKeywords); - - /// - /// Gets the XPSubject exif tag. - /// - public static ExifTag XPSubject => new ExifTag(ExifTagValue.XPSubject); - /// /// Gets the GPSVersionID exif tag. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.EncodedString.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.EncodedString.cs new file mode 100644 index 0000000000..335098a435 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.EncodedString.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the UserComment exif tag. + /// + public static ExifTag UserComment { get; } = new ExifTag(ExifTagValue.UserComment); + + /// + /// Gets the GPSProcessingMethod exif tag. + /// + public static ExifTag GPSProcessingMethod { get; } = new ExifTag(ExifTagValue.GPSProcessingMethod); + + /// + /// Gets the GPSAreaInformation exif tag. + /// + public static ExifTag GPSAreaInformation { get; } = new ExifTag(ExifTagValue.GPSAreaInformation); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Ucs2String.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Ucs2String.cs new file mode 100644 index 0000000000..a6911d76d7 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Ucs2String.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + public abstract partial class ExifTag + { + /// + /// Gets the title tag used by Windows (encoded in UCS2). + /// + public static ExifTag XPTitle => new ExifTag(ExifTagValue.XPTitle); + + /// + /// Gets the comment tag used by Windows (encoded in UCS2). + /// + public static ExifTag XPComment => new ExifTag(ExifTagValue.XPComment); + + /// + /// Gets the author tag used by Windows (encoded in UCS2). + /// + public static ExifTag XPAuthor => new ExifTag(ExifTagValue.XPAuthor); + + /// + /// Gets the keywords tag used by Windows (encoded in UCS2). + /// + public static ExifTag XPKeywords => new ExifTag(ExifTagValue.XPKeywords); + + /// + /// Gets the subject tag used by Windows (encoded in UCS2). + /// + public static ExifTag XPSubject => new ExifTag(ExifTagValue.XPSubject); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs index 1d9af6adce..58886f4036 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs @@ -31,11 +31,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// public static ExifTag MakerNote { get; } = new ExifTag(ExifTagValue.MakerNote); - /// - /// Gets the UserComment exif tag. - /// - public static ExifTag UserComment { get; } = new ExifTag(ExifTagValue.UserComment); - /// /// Gets the FlashpixVersion exif tag. /// @@ -71,16 +66,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// public static ExifTag ImageSourceData { get; } = new ExifTag(ExifTagValue.ImageSourceData); - /// - /// Gets the GPSProcessingMethod exif tag. - /// - public static ExifTag GPSProcessingMethod { get; } = new ExifTag(ExifTagValue.GPSProcessingMethod); - - /// - /// Gets the GPSAreaInformation exif tag. - /// - public static ExifTag GPSAreaInformation { get; } = new ExifTag(ExifTagValue.GPSAreaInformation); - /// /// Gets the FileSource exif tag. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/EncodedString.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/EncodedString.cs new file mode 100644 index 0000000000..e9cd27427c --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/EncodedString.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// The EXIF encoded string structure. + /// + public readonly struct EncodedString : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// Default use Unicode character code. + /// + /// The text value. + public EncodedString(string text) + : this(CharacterCode.Unicode, text) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The character code. + /// The text value. + public EncodedString(CharacterCode code, string text) + { + this.Text = text; + this.Code = code; + } + + /// + /// The 8-byte character code enum. + /// + public enum CharacterCode + { + /// + /// The ASCII (ITU-T T.50 IA5) character code. + /// + ASCII, + + /// + /// The JIS (X208-1990) character code. + /// + JIS, + + /// + /// The Unicode character code. + /// + Unicode, + + /// + /// The undefined character code. + /// + Undefined + } + + /// + /// Gets the character ode. + /// + public CharacterCode Code { get; } + + /// + /// Gets the text. + /// + public string Text { get; } + + /// + /// Converts the specified to an instance of this type. + /// + /// The text value. + public static implicit operator EncodedString(string text) => new(text); + + /// + /// Converts the specified to a . + /// + /// The to convert. + public static explicit operator string(EncodedString encodedString) => encodedString.Text; + + /// + public override bool Equals(object obj) => obj is EncodedString other && this.Equals(other); + + /// + public bool Equals(EncodedString other) => this.Text == other.Text && this.Code == other.Code; + + /// + public override int GetHashCode() => HashCode.Combine(this.Text, this.Code); + + /// + public override string ToString() => this.Text; + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs new file mode 100644 index 0000000000..ba9fca5c8f --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs @@ -0,0 +1,55 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifEncodedString : ExifValue + { + public ExifEncodedString(ExifTag tag) + : base(tag) + { + } + + public ExifEncodedString(ExifTagValue tag) + : base(tag) + { + } + + private ExifEncodedString(ExifEncodedString value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.Undefined; + + protected override string StringValue => this.Value.Text; + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + if (value is string stringValue) + { + this.Value = new EncodedString(stringValue); + return true; + } + else if (value is byte[] buffer) + { + if (ExifEncodedStringHelpers.TryParse(buffer, out EncodedString encodedString)) + { + this.Value = encodedString; + return true; + } + } + + return false; + } + + public override IExifValue DeepClone() => new ExifEncodedString(this); + } +} diff --git a/src/ImageSharp/Processing/OrientationMode.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifOrientationMode.cs similarity index 69% rename from src/ImageSharp/Processing/OrientationMode.cs rename to src/ImageSharp/Metadata/Profiles/Exif/Values/ExifOrientationMode.cs index a8ba5a55c0..b68390ae01 100644 --- a/src/ImageSharp/Processing/OrientationMode.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifOrientationMode.cs @@ -1,56 +1,56 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { /// /// Enumerates the available orientation values supplied by EXIF metadata. /// - internal enum OrientationMode : ushort + public static class ExifOrientationMode { /// /// Unknown rotation. /// - Unknown = 0, + public const ushort Unknown = 0; /// /// The 0th row at the top, the 0th column on the left. /// - TopLeft = 1, + public const ushort TopLeft = 1; /// /// The 0th row at the top, the 0th column on the right. /// - TopRight = 2, + public const ushort TopRight = 2; /// /// The 0th row at the bottom, the 0th column on the right. /// - BottomRight = 3, + public const ushort BottomRight = 3; /// /// The 0th row at the bottom, the 0th column on the left. /// - BottomLeft = 4, + public const ushort BottomLeft = 4; /// /// The 0th row on the left, the 0th column at the top. /// - LeftTop = 5, + public const ushort LeftTop = 5; /// /// The 0th row at the right, the 0th column at the top. /// - RightTop = 6, + public const ushort RightTop = 6; /// /// The 0th row on the right, the 0th column at the bottom. /// - RightBottom = 7, + public const ushort RightBottom = 7; /// /// The 0th row on the left, the 0th column at the bottom. /// - LeftBottom = 8 + public const ushort LeftBottom = 8; } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifUcs2String.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifUcs2String.cs new file mode 100644 index 0000000000..42637925c7 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifUcs2String.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifUcs2String : ExifValue + { + public ExifUcs2String(ExifTag tag) + : base(tag) + { + } + + public ExifUcs2String(ExifTagValue tag) + : base(tag) + { + } + + private ExifUcs2String(ExifUcs2String value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.Byte; + + protected override string StringValue => this.Value; + + public override object GetValue() => this.Value; + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + if (value is byte[] buffer) + { + this.Value = ExifUcs2StringHelpers.Ucs2Encoding.GetString(buffer); + return true; + } + + return false; + } + + public override IExifValue DeepClone() => new ExifUcs2String(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs index 33fb90cc04..fa5cf9b2fa 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs @@ -15,21 +15,36 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { switch (dataType) { - case ExifDataType.Byte: return isArray ? (ExifValue)new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType); - case ExifDataType.DoubleFloat: return isArray ? (ExifValue)new ExifDoubleArray(tag) : new ExifDouble(tag); - case ExifDataType.SingleFloat: return isArray ? (ExifValue)new ExifFloatArray(tag) : new ExifFloat(tag); - case ExifDataType.Long: return isArray ? (ExifValue)new ExifLongArray(tag) : new ExifLong(tag); - case ExifDataType.Long8: return isArray ? (ExifValue)new ExifLong8Array(tag) : new ExifLong8(tag); - case ExifDataType.Rational: return isArray ? (ExifValue)new ExifRationalArray(tag) : new ExifRational(tag); - case ExifDataType.Short: return isArray ? (ExifValue)new ExifShortArray(tag) : new ExifShort(tag); - case ExifDataType.SignedByte: return isArray ? (ExifValue)new ExifSignedByteArray(tag) : new ExifSignedByte(tag); - case ExifDataType.SignedLong: return isArray ? (ExifValue)new ExifSignedLongArray(tag) : new ExifSignedLong(tag); - case ExifDataType.SignedLong8: return isArray ? (ExifValue)new ExifSignedLong8Array(tag) : new ExifSignedLong8(tag); - case ExifDataType.SignedRational: return isArray ? (ExifValue)new ExifSignedRationalArray(tag) : new ExifSignedRational(tag); - case ExifDataType.SignedShort: return isArray ? (ExifValue)new ExifSignedShortArray(tag) : new ExifSignedShort(tag); - case ExifDataType.Ascii: return new ExifString(tag); - case ExifDataType.Undefined: return isArray ? (ExifValue)new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType); - default: return null; + case ExifDataType.Byte: + return isArray ? new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType); + case ExifDataType.DoubleFloat: + return isArray ? new ExifDoubleArray(tag) : new ExifDouble(tag); + case ExifDataType.SingleFloat: + return isArray ? new ExifFloatArray(tag) : new ExifFloat(tag); + case ExifDataType.Long: + return isArray ? new ExifLongArray(tag) : new ExifLong(tag); + case ExifDataType.Long8: + return isArray ? new ExifLong8Array(tag) : new ExifLong8(tag); + case ExifDataType.Rational: + return isArray ? new ExifRationalArray(tag) : new ExifRational(tag); + case ExifDataType.Short: + return isArray ? new ExifShortArray(tag) : new ExifShort(tag); + case ExifDataType.SignedByte: + return isArray ? new ExifSignedByteArray(tag) : new ExifSignedByte(tag); + case ExifDataType.SignedLong: + return isArray ? new ExifSignedLongArray(tag) : new ExifSignedLong(tag); + case ExifDataType.SignedLong8: + return isArray ? new ExifSignedLong8Array(tag) : new ExifSignedLong8(tag); + case ExifDataType.SignedRational: + return isArray ? new ExifSignedRationalArray(tag) : new ExifSignedRational(tag); + case ExifDataType.SignedShort: + return isArray ? new ExifSignedShortArray(tag) : new ExifSignedShort(tag); + case ExifDataType.Ascii: + return new ExifString(tag); + case ExifDataType.Undefined: + return isArray ? new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType); + default: + return null; } } @@ -37,275 +52,530 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { switch (tag) { - case ExifTagValue.FaxProfile: return new ExifByte(ExifTag.FaxProfile, ExifDataType.Byte); - case ExifTagValue.ModeNumber: return new ExifByte(ExifTag.ModeNumber, ExifDataType.Byte); - case ExifTagValue.GPSAltitudeRef: return new ExifByte(ExifTag.GPSAltitudeRef, ExifDataType.Byte); + case ExifTagValue.FaxProfile: + return new ExifByte(ExifTag.FaxProfile, ExifDataType.Byte); + case ExifTagValue.ModeNumber: + return new ExifByte(ExifTag.ModeNumber, ExifDataType.Byte); + case ExifTagValue.GPSAltitudeRef: + return new ExifByte(ExifTag.GPSAltitudeRef, ExifDataType.Byte); - case ExifTagValue.ClipPath: return new ExifByteArray(ExifTag.ClipPath, ExifDataType.Byte); - case ExifTagValue.VersionYear: return new ExifByteArray(ExifTag.VersionYear, ExifDataType.Byte); - case ExifTagValue.XMP: return new ExifByteArray(ExifTag.XMP, ExifDataType.Byte); - case ExifTagValue.CFAPattern2: return new ExifByteArray(ExifTag.CFAPattern2, ExifDataType.Byte); - case ExifTagValue.TIFFEPStandardID: return new ExifByteArray(ExifTag.TIFFEPStandardID, ExifDataType.Byte); - case ExifTagValue.XPTitle: return new ExifByteArray(ExifTag.XPTitle, ExifDataType.Byte); - case ExifTagValue.XPComment: return new ExifByteArray(ExifTag.XPComment, ExifDataType.Byte); - case ExifTagValue.XPAuthor: return new ExifByteArray(ExifTag.XPAuthor, ExifDataType.Byte); - case ExifTagValue.XPKeywords: return new ExifByteArray(ExifTag.XPKeywords, ExifDataType.Byte); - case ExifTagValue.XPSubject: return new ExifByteArray(ExifTag.XPSubject, ExifDataType.Byte); - case ExifTagValue.GPSVersionID: return new ExifByteArray(ExifTag.GPSVersionID, ExifDataType.Byte); + case ExifTagValue.ClipPath: + return new ExifByteArray(ExifTag.ClipPath, ExifDataType.Byte); + case ExifTagValue.VersionYear: + return new ExifByteArray(ExifTag.VersionYear, ExifDataType.Byte); + case ExifTagValue.XMP: + return new ExifByteArray(ExifTag.XMP, ExifDataType.Byte); + case ExifTagValue.CFAPattern2: + return new ExifByteArray(ExifTag.CFAPattern2, ExifDataType.Byte); + case ExifTagValue.TIFFEPStandardID: + return new ExifByteArray(ExifTag.TIFFEPStandardID, ExifDataType.Byte); + case ExifTagValue.GPSVersionID: + return new ExifByteArray(ExifTag.GPSVersionID, ExifDataType.Byte); - case ExifTagValue.PixelScale: return new ExifDoubleArray(ExifTag.PixelScale); - case ExifTagValue.IntergraphMatrix: return new ExifDoubleArray(ExifTag.IntergraphMatrix); - case ExifTagValue.ModelTiePoint: return new ExifDoubleArray(ExifTag.ModelTiePoint); - case ExifTagValue.ModelTransform: return new ExifDoubleArray(ExifTag.ModelTransform); + case ExifTagValue.PixelScale: + return new ExifDoubleArray(ExifTag.PixelScale); + case ExifTagValue.IntergraphMatrix: + return new ExifDoubleArray(ExifTag.IntergraphMatrix); + case ExifTagValue.ModelTiePoint: + return new ExifDoubleArray(ExifTag.ModelTiePoint); + case ExifTagValue.ModelTransform: + return new ExifDoubleArray(ExifTag.ModelTransform); - case ExifTagValue.SubfileType: return new ExifLong(ExifTag.SubfileType); - case ExifTagValue.SubIFDOffset: return new ExifLong(ExifTag.SubIFDOffset); - case ExifTagValue.GPSIFDOffset: return new ExifLong(ExifTag.GPSIFDOffset); - case ExifTagValue.T4Options: return new ExifLong(ExifTag.T4Options); - case ExifTagValue.T6Options: return new ExifLong(ExifTag.T6Options); - case ExifTagValue.XClipPathUnits: return new ExifLong(ExifTag.XClipPathUnits); - case ExifTagValue.YClipPathUnits: return new ExifLong(ExifTag.YClipPathUnits); - case ExifTagValue.ProfileType: return new ExifLong(ExifTag.ProfileType); - case ExifTagValue.CodingMethods: return new ExifLong(ExifTag.CodingMethods); - case ExifTagValue.T82ptions: return new ExifLong(ExifTag.T82ptions); - case ExifTagValue.JPEGInterchangeFormat: return new ExifLong(ExifTag.JPEGInterchangeFormat); - case ExifTagValue.JPEGInterchangeFormatLength: return new ExifLong(ExifTag.JPEGInterchangeFormatLength); - case ExifTagValue.MDFileTag: return new ExifLong(ExifTag.MDFileTag); - case ExifTagValue.StandardOutputSensitivity: return new ExifLong(ExifTag.StandardOutputSensitivity); - case ExifTagValue.RecommendedExposureIndex: return new ExifLong(ExifTag.RecommendedExposureIndex); - case ExifTagValue.ISOSpeed: return new ExifLong(ExifTag.ISOSpeed); - case ExifTagValue.ISOSpeedLatitudeyyy: return new ExifLong(ExifTag.ISOSpeedLatitudeyyy); - case ExifTagValue.ISOSpeedLatitudezzz: return new ExifLong(ExifTag.ISOSpeedLatitudezzz); - case ExifTagValue.FaxRecvParams: return new ExifLong(ExifTag.FaxRecvParams); - case ExifTagValue.FaxRecvTime: return new ExifLong(ExifTag.FaxRecvTime); - case ExifTagValue.ImageNumber: return new ExifLong(ExifTag.ImageNumber); + case ExifTagValue.SubfileType: + return new ExifLong(ExifTag.SubfileType); + case ExifTagValue.SubIFDOffset: + return new ExifLong(ExifTag.SubIFDOffset); + case ExifTagValue.GPSIFDOffset: + return new ExifLong(ExifTag.GPSIFDOffset); + case ExifTagValue.T4Options: + return new ExifLong(ExifTag.T4Options); + case ExifTagValue.T6Options: + return new ExifLong(ExifTag.T6Options); + case ExifTagValue.XClipPathUnits: + return new ExifLong(ExifTag.XClipPathUnits); + case ExifTagValue.YClipPathUnits: + return new ExifLong(ExifTag.YClipPathUnits); + case ExifTagValue.ProfileType: + return new ExifLong(ExifTag.ProfileType); + case ExifTagValue.CodingMethods: + return new ExifLong(ExifTag.CodingMethods); + case ExifTagValue.T82ptions: + return new ExifLong(ExifTag.T82ptions); + case ExifTagValue.JPEGInterchangeFormat: + return new ExifLong(ExifTag.JPEGInterchangeFormat); + case ExifTagValue.JPEGInterchangeFormatLength: + return new ExifLong(ExifTag.JPEGInterchangeFormatLength); + case ExifTagValue.MDFileTag: + return new ExifLong(ExifTag.MDFileTag); + case ExifTagValue.StandardOutputSensitivity: + return new ExifLong(ExifTag.StandardOutputSensitivity); + case ExifTagValue.RecommendedExposureIndex: + return new ExifLong(ExifTag.RecommendedExposureIndex); + case ExifTagValue.ISOSpeed: + return new ExifLong(ExifTag.ISOSpeed); + case ExifTagValue.ISOSpeedLatitudeyyy: + return new ExifLong(ExifTag.ISOSpeedLatitudeyyy); + case ExifTagValue.ISOSpeedLatitudezzz: + return new ExifLong(ExifTag.ISOSpeedLatitudezzz); + case ExifTagValue.FaxRecvParams: + return new ExifLong(ExifTag.FaxRecvParams); + case ExifTagValue.FaxRecvTime: + return new ExifLong(ExifTag.FaxRecvTime); + case ExifTagValue.ImageNumber: + return new ExifLong(ExifTag.ImageNumber); - case ExifTagValue.FreeOffsets: return new ExifLongArray(ExifTag.FreeOffsets); - case ExifTagValue.FreeByteCounts: return new ExifLongArray(ExifTag.FreeByteCounts); - case ExifTagValue.ColorResponseUnit: return new ExifLongArray(ExifTag.ColorResponseUnit); - case ExifTagValue.TileOffsets: return new ExifLongArray(ExifTag.TileOffsets); - case ExifTagValue.SMinSampleValue: return new ExifLongArray(ExifTag.SMinSampleValue); - case ExifTagValue.SMaxSampleValue: return new ExifLongArray(ExifTag.SMaxSampleValue); - case ExifTagValue.JPEGQTables: return new ExifLongArray(ExifTag.JPEGQTables); - case ExifTagValue.JPEGDCTables: return new ExifLongArray(ExifTag.JPEGDCTables); - case ExifTagValue.JPEGACTables: return new ExifLongArray(ExifTag.JPEGACTables); - case ExifTagValue.StripRowCounts: return new ExifLongArray(ExifTag.StripRowCounts); - case ExifTagValue.IntergraphRegisters: return new ExifLongArray(ExifTag.IntergraphRegisters); - case ExifTagValue.TimeZoneOffset: return new ExifLongArray(ExifTag.TimeZoneOffset); - case ExifTagValue.SubIFDs: return new ExifLongArray(ExifTag.SubIFDs); + case ExifTagValue.FreeOffsets: + return new ExifLongArray(ExifTag.FreeOffsets); + case ExifTagValue.FreeByteCounts: + return new ExifLongArray(ExifTag.FreeByteCounts); + case ExifTagValue.ColorResponseUnit: + return new ExifLongArray(ExifTag.ColorResponseUnit); + case ExifTagValue.TileOffsets: + return new ExifLongArray(ExifTag.TileOffsets); + case ExifTagValue.SMinSampleValue: + return new ExifLongArray(ExifTag.SMinSampleValue); + case ExifTagValue.SMaxSampleValue: + return new ExifLongArray(ExifTag.SMaxSampleValue); + case ExifTagValue.JPEGQTables: + return new ExifLongArray(ExifTag.JPEGQTables); + case ExifTagValue.JPEGDCTables: + return new ExifLongArray(ExifTag.JPEGDCTables); + case ExifTagValue.JPEGACTables: + return new ExifLongArray(ExifTag.JPEGACTables); + case ExifTagValue.StripRowCounts: + return new ExifLongArray(ExifTag.StripRowCounts); + case ExifTagValue.IntergraphRegisters: + return new ExifLongArray(ExifTag.IntergraphRegisters); + case ExifTagValue.TimeZoneOffset: + return new ExifLongArray(ExifTag.TimeZoneOffset); + case ExifTagValue.SubIFDs: + return new ExifLongArray(ExifTag.SubIFDs); - case ExifTagValue.ImageWidth: return new ExifNumber(ExifTag.ImageWidth); - case ExifTagValue.ImageLength: return new ExifNumber(ExifTag.ImageLength); - case ExifTagValue.RowsPerStrip: return new ExifNumber(ExifTag.RowsPerStrip); - case ExifTagValue.TileWidth: return new ExifNumber(ExifTag.TileWidth); - case ExifTagValue.TileLength: return new ExifNumber(ExifTag.TileLength); - case ExifTagValue.BadFaxLines: return new ExifNumber(ExifTag.BadFaxLines); - case ExifTagValue.ConsecutiveBadFaxLines: return new ExifNumber(ExifTag.ConsecutiveBadFaxLines); - case ExifTagValue.PixelXDimension: return new ExifNumber(ExifTag.PixelXDimension); - case ExifTagValue.PixelYDimension: return new ExifNumber(ExifTag.PixelYDimension); + case ExifTagValue.ImageWidth: + return new ExifNumber(ExifTag.ImageWidth); + case ExifTagValue.ImageLength: + return new ExifNumber(ExifTag.ImageLength); + case ExifTagValue.RowsPerStrip: + return new ExifNumber(ExifTag.RowsPerStrip); + case ExifTagValue.TileWidth: + return new ExifNumber(ExifTag.TileWidth); + case ExifTagValue.TileLength: + return new ExifNumber(ExifTag.TileLength); + case ExifTagValue.BadFaxLines: + return new ExifNumber(ExifTag.BadFaxLines); + case ExifTagValue.ConsecutiveBadFaxLines: + return new ExifNumber(ExifTag.ConsecutiveBadFaxLines); + case ExifTagValue.PixelXDimension: + return new ExifNumber(ExifTag.PixelXDimension); + case ExifTagValue.PixelYDimension: + return new ExifNumber(ExifTag.PixelYDimension); - case ExifTagValue.StripByteCounts: return new ExifNumberArray(ExifTag.StripByteCounts); - case ExifTagValue.StripOffsets: return new ExifNumberArray(ExifTag.StripOffsets); - case ExifTagValue.TileByteCounts: return new ExifNumberArray(ExifTag.TileByteCounts); - case ExifTagValue.ImageLayer: return new ExifNumberArray(ExifTag.ImageLayer); + case ExifTagValue.StripByteCounts: + return new ExifNumberArray(ExifTag.StripByteCounts); + case ExifTagValue.StripOffsets: + return new ExifNumberArray(ExifTag.StripOffsets); + case ExifTagValue.TileByteCounts: + return new ExifNumberArray(ExifTag.TileByteCounts); + case ExifTagValue.ImageLayer: + return new ExifNumberArray(ExifTag.ImageLayer); - case ExifTagValue.XPosition: return new ExifRational(ExifTag.XPosition); - case ExifTagValue.YPosition: return new ExifRational(ExifTag.YPosition); - case ExifTagValue.XResolution: return new ExifRational(ExifTag.XResolution); - case ExifTagValue.YResolution: return new ExifRational(ExifTag.YResolution); - case ExifTagValue.BatteryLevel: return new ExifRational(ExifTag.BatteryLevel); - case ExifTagValue.ExposureTime: return new ExifRational(ExifTag.ExposureTime); - case ExifTagValue.FNumber: return new ExifRational(ExifTag.FNumber); - case ExifTagValue.MDScalePixel: return new ExifRational(ExifTag.MDScalePixel); - case ExifTagValue.CompressedBitsPerPixel: return new ExifRational(ExifTag.CompressedBitsPerPixel); - case ExifTagValue.ApertureValue: return new ExifRational(ExifTag.ApertureValue); - case ExifTagValue.MaxApertureValue: return new ExifRational(ExifTag.MaxApertureValue); - case ExifTagValue.SubjectDistance: return new ExifRational(ExifTag.SubjectDistance); - case ExifTagValue.FocalLength: return new ExifRational(ExifTag.FocalLength); - case ExifTagValue.FlashEnergy2: return new ExifRational(ExifTag.FlashEnergy2); - case ExifTagValue.FocalPlaneXResolution2: return new ExifRational(ExifTag.FocalPlaneXResolution2); - case ExifTagValue.FocalPlaneYResolution2: return new ExifRational(ExifTag.FocalPlaneYResolution2); - case ExifTagValue.ExposureIndex2: return new ExifRational(ExifTag.ExposureIndex2); - case ExifTagValue.Humidity: return new ExifRational(ExifTag.Humidity); - case ExifTagValue.Pressure: return new ExifRational(ExifTag.Pressure); - case ExifTagValue.Acceleration: return new ExifRational(ExifTag.Acceleration); - case ExifTagValue.FlashEnergy: return new ExifRational(ExifTag.FlashEnergy); - case ExifTagValue.FocalPlaneXResolution: return new ExifRational(ExifTag.FocalPlaneXResolution); - case ExifTagValue.FocalPlaneYResolution: return new ExifRational(ExifTag.FocalPlaneYResolution); - case ExifTagValue.ExposureIndex: return new ExifRational(ExifTag.ExposureIndex); - case ExifTagValue.DigitalZoomRatio: return new ExifRational(ExifTag.DigitalZoomRatio); - case ExifTagValue.GPSAltitude: return new ExifRational(ExifTag.GPSAltitude); - case ExifTagValue.GPSDOP: return new ExifRational(ExifTag.GPSDOP); - case ExifTagValue.GPSSpeed: return new ExifRational(ExifTag.GPSSpeed); - case ExifTagValue.GPSTrack: return new ExifRational(ExifTag.GPSTrack); - case ExifTagValue.GPSImgDirection: return new ExifRational(ExifTag.GPSImgDirection); - case ExifTagValue.GPSDestBearing: return new ExifRational(ExifTag.GPSDestBearing); - case ExifTagValue.GPSDestDistance: return new ExifRational(ExifTag.GPSDestDistance); + case ExifTagValue.XPosition: + return new ExifRational(ExifTag.XPosition); + case ExifTagValue.YPosition: + return new ExifRational(ExifTag.YPosition); + case ExifTagValue.XResolution: + return new ExifRational(ExifTag.XResolution); + case ExifTagValue.YResolution: + return new ExifRational(ExifTag.YResolution); + case ExifTagValue.BatteryLevel: + return new ExifRational(ExifTag.BatteryLevel); + case ExifTagValue.ExposureTime: + return new ExifRational(ExifTag.ExposureTime); + case ExifTagValue.FNumber: + return new ExifRational(ExifTag.FNumber); + case ExifTagValue.MDScalePixel: + return new ExifRational(ExifTag.MDScalePixel); + case ExifTagValue.CompressedBitsPerPixel: + return new ExifRational(ExifTag.CompressedBitsPerPixel); + case ExifTagValue.ApertureValue: + return new ExifRational(ExifTag.ApertureValue); + case ExifTagValue.MaxApertureValue: + return new ExifRational(ExifTag.MaxApertureValue); + case ExifTagValue.SubjectDistance: + return new ExifRational(ExifTag.SubjectDistance); + case ExifTagValue.FocalLength: + return new ExifRational(ExifTag.FocalLength); + case ExifTagValue.FlashEnergy2: + return new ExifRational(ExifTag.FlashEnergy2); + case ExifTagValue.FocalPlaneXResolution2: + return new ExifRational(ExifTag.FocalPlaneXResolution2); + case ExifTagValue.FocalPlaneYResolution2: + return new ExifRational(ExifTag.FocalPlaneYResolution2); + case ExifTagValue.ExposureIndex2: + return new ExifRational(ExifTag.ExposureIndex2); + case ExifTagValue.Humidity: + return new ExifRational(ExifTag.Humidity); + case ExifTagValue.Pressure: + return new ExifRational(ExifTag.Pressure); + case ExifTagValue.Acceleration: + return new ExifRational(ExifTag.Acceleration); + case ExifTagValue.FlashEnergy: + return new ExifRational(ExifTag.FlashEnergy); + case ExifTagValue.FocalPlaneXResolution: + return new ExifRational(ExifTag.FocalPlaneXResolution); + case ExifTagValue.FocalPlaneYResolution: + return new ExifRational(ExifTag.FocalPlaneYResolution); + case ExifTagValue.ExposureIndex: + return new ExifRational(ExifTag.ExposureIndex); + case ExifTagValue.DigitalZoomRatio: + return new ExifRational(ExifTag.DigitalZoomRatio); + case ExifTagValue.GPSAltitude: + return new ExifRational(ExifTag.GPSAltitude); + case ExifTagValue.GPSDOP: + return new ExifRational(ExifTag.GPSDOP); + case ExifTagValue.GPSSpeed: + return new ExifRational(ExifTag.GPSSpeed); + case ExifTagValue.GPSTrack: + return new ExifRational(ExifTag.GPSTrack); + case ExifTagValue.GPSImgDirection: + return new ExifRational(ExifTag.GPSImgDirection); + case ExifTagValue.GPSDestBearing: + return new ExifRational(ExifTag.GPSDestBearing); + case ExifTagValue.GPSDestDistance: + return new ExifRational(ExifTag.GPSDestDistance); - case ExifTagValue.WhitePoint: return new ExifRationalArray(ExifTag.WhitePoint); - case ExifTagValue.PrimaryChromaticities: return new ExifRationalArray(ExifTag.PrimaryChromaticities); - case ExifTagValue.YCbCrCoefficients: return new ExifRationalArray(ExifTag.YCbCrCoefficients); - case ExifTagValue.ReferenceBlackWhite: return new ExifRationalArray(ExifTag.ReferenceBlackWhite); - case ExifTagValue.GPSLatitude: return new ExifRationalArray(ExifTag.GPSLatitude); - case ExifTagValue.GPSLongitude: return new ExifRationalArray(ExifTag.GPSLongitude); - case ExifTagValue.GPSTimestamp: return new ExifRationalArray(ExifTag.GPSTimestamp); - case ExifTagValue.GPSDestLatitude: return new ExifRationalArray(ExifTag.GPSDestLatitude); - case ExifTagValue.GPSDestLongitude: return new ExifRationalArray(ExifTag.GPSDestLongitude); - case ExifTagValue.LensSpecification: return new ExifRationalArray(ExifTag.LensSpecification); + case ExifTagValue.WhitePoint: + return new ExifRationalArray(ExifTag.WhitePoint); + case ExifTagValue.PrimaryChromaticities: + return new ExifRationalArray(ExifTag.PrimaryChromaticities); + case ExifTagValue.YCbCrCoefficients: + return new ExifRationalArray(ExifTag.YCbCrCoefficients); + case ExifTagValue.ReferenceBlackWhite: + return new ExifRationalArray(ExifTag.ReferenceBlackWhite); + case ExifTagValue.GPSLatitude: + return new ExifRationalArray(ExifTag.GPSLatitude); + case ExifTagValue.GPSLongitude: + return new ExifRationalArray(ExifTag.GPSLongitude); + case ExifTagValue.GPSTimestamp: + return new ExifRationalArray(ExifTag.GPSTimestamp); + case ExifTagValue.GPSDestLatitude: + return new ExifRationalArray(ExifTag.GPSDestLatitude); + case ExifTagValue.GPSDestLongitude: + return new ExifRationalArray(ExifTag.GPSDestLongitude); + case ExifTagValue.LensSpecification: + return new ExifRationalArray(ExifTag.LensSpecification); - case ExifTagValue.OldSubfileType: return new ExifShort(ExifTag.OldSubfileType); - case ExifTagValue.Compression: return new ExifShort(ExifTag.Compression); - case ExifTagValue.PhotometricInterpretation: return new ExifShort(ExifTag.PhotometricInterpretation); - case ExifTagValue.Thresholding: return new ExifShort(ExifTag.Thresholding); - case ExifTagValue.CellWidth: return new ExifShort(ExifTag.CellWidth); - case ExifTagValue.CellLength: return new ExifShort(ExifTag.CellLength); - case ExifTagValue.FillOrder: return new ExifShort(ExifTag.FillOrder); - case ExifTagValue.Orientation: return new ExifShort(ExifTag.Orientation); - case ExifTagValue.SamplesPerPixel: return new ExifShort(ExifTag.SamplesPerPixel); - case ExifTagValue.PlanarConfiguration: return new ExifShort(ExifTag.PlanarConfiguration); - case ExifTagValue.Predictor: return new ExifShort(ExifTag.Predictor); - case ExifTagValue.GrayResponseUnit: return new ExifShort(ExifTag.GrayResponseUnit); - case ExifTagValue.ResolutionUnit: return new ExifShort(ExifTag.ResolutionUnit); - case ExifTagValue.CleanFaxData: return new ExifShort(ExifTag.CleanFaxData); - case ExifTagValue.InkSet: return new ExifShort(ExifTag.InkSet); - case ExifTagValue.NumberOfInks: return new ExifShort(ExifTag.NumberOfInks); - case ExifTagValue.DotRange: return new ExifShort(ExifTag.DotRange); - case ExifTagValue.Indexed: return new ExifShort(ExifTag.Indexed); - case ExifTagValue.OPIProxy: return new ExifShort(ExifTag.OPIProxy); - case ExifTagValue.JPEGProc: return new ExifShort(ExifTag.JPEGProc); - case ExifTagValue.JPEGRestartInterval: return new ExifShort(ExifTag.JPEGRestartInterval); - case ExifTagValue.YCbCrPositioning: return new ExifShort(ExifTag.YCbCrPositioning); - case ExifTagValue.Rating: return new ExifShort(ExifTag.Rating); - case ExifTagValue.RatingPercent: return new ExifShort(ExifTag.RatingPercent); - case ExifTagValue.ExposureProgram: return new ExifShort(ExifTag.ExposureProgram); - case ExifTagValue.Interlace: return new ExifShort(ExifTag.Interlace); - case ExifTagValue.SelfTimerMode: return new ExifShort(ExifTag.SelfTimerMode); - case ExifTagValue.SensitivityType: return new ExifShort(ExifTag.SensitivityType); - case ExifTagValue.MeteringMode: return new ExifShort(ExifTag.MeteringMode); - case ExifTagValue.LightSource: return new ExifShort(ExifTag.LightSource); - case ExifTagValue.FocalPlaneResolutionUnit2: return new ExifShort(ExifTag.FocalPlaneResolutionUnit2); - case ExifTagValue.SensingMethod2: return new ExifShort(ExifTag.SensingMethod2); - case ExifTagValue.Flash: return new ExifShort(ExifTag.Flash); - case ExifTagValue.ColorSpace: return new ExifShort(ExifTag.ColorSpace); - case ExifTagValue.FocalPlaneResolutionUnit: return new ExifShort(ExifTag.FocalPlaneResolutionUnit); - case ExifTagValue.SensingMethod: return new ExifShort(ExifTag.SensingMethod); - case ExifTagValue.CustomRendered: return new ExifShort(ExifTag.CustomRendered); - case ExifTagValue.ExposureMode: return new ExifShort(ExifTag.ExposureMode); - case ExifTagValue.WhiteBalance: return new ExifShort(ExifTag.WhiteBalance); - case ExifTagValue.FocalLengthIn35mmFilm: return new ExifShort(ExifTag.FocalLengthIn35mmFilm); - case ExifTagValue.SceneCaptureType: return new ExifShort(ExifTag.SceneCaptureType); - case ExifTagValue.GainControl: return new ExifShort(ExifTag.GainControl); - case ExifTagValue.Contrast: return new ExifShort(ExifTag.Contrast); - case ExifTagValue.Saturation: return new ExifShort(ExifTag.Saturation); - case ExifTagValue.Sharpness: return new ExifShort(ExifTag.Sharpness); - case ExifTagValue.SubjectDistanceRange: return new ExifShort(ExifTag.SubjectDistanceRange); - case ExifTagValue.GPSDifferential: return new ExifShort(ExifTag.GPSDifferential); + case ExifTagValue.OldSubfileType: + return new ExifShort(ExifTag.OldSubfileType); + case ExifTagValue.Compression: + return new ExifShort(ExifTag.Compression); + case ExifTagValue.PhotometricInterpretation: + return new ExifShort(ExifTag.PhotometricInterpretation); + case ExifTagValue.Thresholding: + return new ExifShort(ExifTag.Thresholding); + case ExifTagValue.CellWidth: + return new ExifShort(ExifTag.CellWidth); + case ExifTagValue.CellLength: + return new ExifShort(ExifTag.CellLength); + case ExifTagValue.FillOrder: + return new ExifShort(ExifTag.FillOrder); + case ExifTagValue.Orientation: + return new ExifShort(ExifTag.Orientation); + case ExifTagValue.SamplesPerPixel: + return new ExifShort(ExifTag.SamplesPerPixel); + case ExifTagValue.PlanarConfiguration: + return new ExifShort(ExifTag.PlanarConfiguration); + case ExifTagValue.Predictor: + return new ExifShort(ExifTag.Predictor); + case ExifTagValue.GrayResponseUnit: + return new ExifShort(ExifTag.GrayResponseUnit); + case ExifTagValue.ResolutionUnit: + return new ExifShort(ExifTag.ResolutionUnit); + case ExifTagValue.CleanFaxData: + return new ExifShort(ExifTag.CleanFaxData); + case ExifTagValue.InkSet: + return new ExifShort(ExifTag.InkSet); + case ExifTagValue.NumberOfInks: + return new ExifShort(ExifTag.NumberOfInks); + case ExifTagValue.DotRange: + return new ExifShort(ExifTag.DotRange); + case ExifTagValue.Indexed: + return new ExifShort(ExifTag.Indexed); + case ExifTagValue.OPIProxy: + return new ExifShort(ExifTag.OPIProxy); + case ExifTagValue.JPEGProc: + return new ExifShort(ExifTag.JPEGProc); + case ExifTagValue.JPEGRestartInterval: + return new ExifShort(ExifTag.JPEGRestartInterval); + case ExifTagValue.YCbCrPositioning: + return new ExifShort(ExifTag.YCbCrPositioning); + case ExifTagValue.Rating: + return new ExifShort(ExifTag.Rating); + case ExifTagValue.RatingPercent: + return new ExifShort(ExifTag.RatingPercent); + case ExifTagValue.ExposureProgram: + return new ExifShort(ExifTag.ExposureProgram); + case ExifTagValue.Interlace: + return new ExifShort(ExifTag.Interlace); + case ExifTagValue.SelfTimerMode: + return new ExifShort(ExifTag.SelfTimerMode); + case ExifTagValue.SensitivityType: + return new ExifShort(ExifTag.SensitivityType); + case ExifTagValue.MeteringMode: + return new ExifShort(ExifTag.MeteringMode); + case ExifTagValue.LightSource: + return new ExifShort(ExifTag.LightSource); + case ExifTagValue.FocalPlaneResolutionUnit2: + return new ExifShort(ExifTag.FocalPlaneResolutionUnit2); + case ExifTagValue.SensingMethod2: + return new ExifShort(ExifTag.SensingMethod2); + case ExifTagValue.Flash: + return new ExifShort(ExifTag.Flash); + case ExifTagValue.ColorSpace: + return new ExifShort(ExifTag.ColorSpace); + case ExifTagValue.FocalPlaneResolutionUnit: + return new ExifShort(ExifTag.FocalPlaneResolutionUnit); + case ExifTagValue.SensingMethod: + return new ExifShort(ExifTag.SensingMethod); + case ExifTagValue.CustomRendered: + return new ExifShort(ExifTag.CustomRendered); + case ExifTagValue.ExposureMode: + return new ExifShort(ExifTag.ExposureMode); + case ExifTagValue.WhiteBalance: + return new ExifShort(ExifTag.WhiteBalance); + case ExifTagValue.FocalLengthIn35mmFilm: + return new ExifShort(ExifTag.FocalLengthIn35mmFilm); + case ExifTagValue.SceneCaptureType: + return new ExifShort(ExifTag.SceneCaptureType); + case ExifTagValue.GainControl: + return new ExifShort(ExifTag.GainControl); + case ExifTagValue.Contrast: + return new ExifShort(ExifTag.Contrast); + case ExifTagValue.Saturation: + return new ExifShort(ExifTag.Saturation); + case ExifTagValue.Sharpness: + return new ExifShort(ExifTag.Sharpness); + case ExifTagValue.SubjectDistanceRange: + return new ExifShort(ExifTag.SubjectDistanceRange); + case ExifTagValue.GPSDifferential: + return new ExifShort(ExifTag.GPSDifferential); - case ExifTagValue.BitsPerSample: return new ExifShortArray(ExifTag.BitsPerSample); - case ExifTagValue.MinSampleValue: return new ExifShortArray(ExifTag.MinSampleValue); - case ExifTagValue.MaxSampleValue: return new ExifShortArray(ExifTag.MaxSampleValue); - case ExifTagValue.GrayResponseCurve: return new ExifShortArray(ExifTag.GrayResponseCurve); - case ExifTagValue.ColorMap: return new ExifShortArray(ExifTag.ColorMap); - case ExifTagValue.ExtraSamples: return new ExifShortArray(ExifTag.ExtraSamples); - case ExifTagValue.PageNumber: return new ExifShortArray(ExifTag.PageNumber); - case ExifTagValue.TransferFunction: return new ExifShortArray(ExifTag.TransferFunction); - case ExifTagValue.HalftoneHints: return new ExifShortArray(ExifTag.HalftoneHints); - case ExifTagValue.SampleFormat: return new ExifShortArray(ExifTag.SampleFormat); - case ExifTagValue.TransferRange: return new ExifShortArray(ExifTag.TransferRange); - case ExifTagValue.DefaultImageColor: return new ExifShortArray(ExifTag.DefaultImageColor); - case ExifTagValue.JPEGLosslessPredictors: return new ExifShortArray(ExifTag.JPEGLosslessPredictors); - case ExifTagValue.JPEGPointTransforms: return new ExifShortArray(ExifTag.JPEGPointTransforms); - case ExifTagValue.YCbCrSubsampling: return new ExifShortArray(ExifTag.YCbCrSubsampling); - case ExifTagValue.CFARepeatPatternDim: return new ExifShortArray(ExifTag.CFARepeatPatternDim); - case ExifTagValue.IntergraphPacketData: return new ExifShortArray(ExifTag.IntergraphPacketData); - case ExifTagValue.ISOSpeedRatings: return new ExifShortArray(ExifTag.ISOSpeedRatings); - case ExifTagValue.SubjectArea: return new ExifShortArray(ExifTag.SubjectArea); - case ExifTagValue.SubjectLocation: return new ExifShortArray(ExifTag.SubjectLocation); + case ExifTagValue.BitsPerSample: + return new ExifShortArray(ExifTag.BitsPerSample); + case ExifTagValue.MinSampleValue: + return new ExifShortArray(ExifTag.MinSampleValue); + case ExifTagValue.MaxSampleValue: + return new ExifShortArray(ExifTag.MaxSampleValue); + case ExifTagValue.GrayResponseCurve: + return new ExifShortArray(ExifTag.GrayResponseCurve); + case ExifTagValue.ColorMap: + return new ExifShortArray(ExifTag.ColorMap); + case ExifTagValue.ExtraSamples: + return new ExifShortArray(ExifTag.ExtraSamples); + case ExifTagValue.PageNumber: + return new ExifShortArray(ExifTag.PageNumber); + case ExifTagValue.TransferFunction: + return new ExifShortArray(ExifTag.TransferFunction); + case ExifTagValue.HalftoneHints: + return new ExifShortArray(ExifTag.HalftoneHints); + case ExifTagValue.SampleFormat: + return new ExifShortArray(ExifTag.SampleFormat); + case ExifTagValue.TransferRange: + return new ExifShortArray(ExifTag.TransferRange); + case ExifTagValue.DefaultImageColor: + return new ExifShortArray(ExifTag.DefaultImageColor); + case ExifTagValue.JPEGLosslessPredictors: + return new ExifShortArray(ExifTag.JPEGLosslessPredictors); + case ExifTagValue.JPEGPointTransforms: + return new ExifShortArray(ExifTag.JPEGPointTransforms); + case ExifTagValue.YCbCrSubsampling: + return new ExifShortArray(ExifTag.YCbCrSubsampling); + case ExifTagValue.CFARepeatPatternDim: + return new ExifShortArray(ExifTag.CFARepeatPatternDim); + case ExifTagValue.IntergraphPacketData: + return new ExifShortArray(ExifTag.IntergraphPacketData); + case ExifTagValue.ISOSpeedRatings: + return new ExifShortArray(ExifTag.ISOSpeedRatings); + case ExifTagValue.SubjectArea: + return new ExifShortArray(ExifTag.SubjectArea); + case ExifTagValue.SubjectLocation: + return new ExifShortArray(ExifTag.SubjectLocation); - case ExifTagValue.ShutterSpeedValue: return new ExifSignedRational(ExifTag.ShutterSpeedValue); - case ExifTagValue.BrightnessValue: return new ExifSignedRational(ExifTag.BrightnessValue); - case ExifTagValue.ExposureBiasValue: return new ExifSignedRational(ExifTag.ExposureBiasValue); - case ExifTagValue.AmbientTemperature: return new ExifSignedRational(ExifTag.AmbientTemperature); - case ExifTagValue.WaterDepth: return new ExifSignedRational(ExifTag.WaterDepth); - case ExifTagValue.CameraElevationAngle: return new ExifSignedRational(ExifTag.CameraElevationAngle); + case ExifTagValue.ShutterSpeedValue: + return new ExifSignedRational(ExifTag.ShutterSpeedValue); + case ExifTagValue.BrightnessValue: + return new ExifSignedRational(ExifTag.BrightnessValue); + case ExifTagValue.ExposureBiasValue: + return new ExifSignedRational(ExifTag.ExposureBiasValue); + case ExifTagValue.AmbientTemperature: + return new ExifSignedRational(ExifTag.AmbientTemperature); + case ExifTagValue.WaterDepth: + return new ExifSignedRational(ExifTag.WaterDepth); + case ExifTagValue.CameraElevationAngle: + return new ExifSignedRational(ExifTag.CameraElevationAngle); - case ExifTagValue.Decode: return new ExifSignedRationalArray(ExifTag.Decode); + case ExifTagValue.Decode: + return new ExifSignedRationalArray(ExifTag.Decode); - case ExifTagValue.ImageDescription: return new ExifString(ExifTag.ImageDescription); - case ExifTagValue.Make: return new ExifString(ExifTag.Make); - case ExifTagValue.Model: return new ExifString(ExifTag.Model); - case ExifTagValue.Software: return new ExifString(ExifTag.Software); - case ExifTagValue.DateTime: return new ExifString(ExifTag.DateTime); - case ExifTagValue.Artist: return new ExifString(ExifTag.Artist); - case ExifTagValue.HostComputer: return new ExifString(ExifTag.HostComputer); - case ExifTagValue.Copyright: return new ExifString(ExifTag.Copyright); - case ExifTagValue.DocumentName: return new ExifString(ExifTag.DocumentName); - case ExifTagValue.PageName: return new ExifString(ExifTag.PageName); - case ExifTagValue.InkNames: return new ExifString(ExifTag.InkNames); - case ExifTagValue.TargetPrinter: return new ExifString(ExifTag.TargetPrinter); - case ExifTagValue.ImageID: return new ExifString(ExifTag.ImageID); - case ExifTagValue.MDLabName: return new ExifString(ExifTag.MDLabName); - case ExifTagValue.MDSampleInfo: return new ExifString(ExifTag.MDSampleInfo); - case ExifTagValue.MDPrepDate: return new ExifString(ExifTag.MDPrepDate); - case ExifTagValue.MDPrepTime: return new ExifString(ExifTag.MDPrepTime); - case ExifTagValue.MDFileUnits: return new ExifString(ExifTag.MDFileUnits); - case ExifTagValue.SEMInfo: return new ExifString(ExifTag.SEMInfo); - case ExifTagValue.SpectralSensitivity: return new ExifString(ExifTag.SpectralSensitivity); - case ExifTagValue.DateTimeOriginal: return new ExifString(ExifTag.DateTimeOriginal); - case ExifTagValue.DateTimeDigitized: return new ExifString(ExifTag.DateTimeDigitized); - case ExifTagValue.SubsecTime: return new ExifString(ExifTag.SubsecTime); - case ExifTagValue.SubsecTimeOriginal: return new ExifString(ExifTag.SubsecTimeOriginal); - case ExifTagValue.SubsecTimeDigitized: return new ExifString(ExifTag.SubsecTimeDigitized); - case ExifTagValue.RelatedSoundFile: return new ExifString(ExifTag.RelatedSoundFile); - case ExifTagValue.FaxSubaddress: return new ExifString(ExifTag.FaxSubaddress); - case ExifTagValue.OffsetTime: return new ExifString(ExifTag.OffsetTime); - case ExifTagValue.OffsetTimeOriginal: return new ExifString(ExifTag.OffsetTimeOriginal); - case ExifTagValue.OffsetTimeDigitized: return new ExifString(ExifTag.OffsetTimeDigitized); - case ExifTagValue.SecurityClassification: return new ExifString(ExifTag.SecurityClassification); - case ExifTagValue.ImageHistory: return new ExifString(ExifTag.ImageHistory); - case ExifTagValue.ImageUniqueID: return new ExifString(ExifTag.ImageUniqueID); - case ExifTagValue.OwnerName: return new ExifString(ExifTag.OwnerName); - case ExifTagValue.SerialNumber: return new ExifString(ExifTag.SerialNumber); - case ExifTagValue.LensMake: return new ExifString(ExifTag.LensMake); - case ExifTagValue.LensModel: return new ExifString(ExifTag.LensModel); - case ExifTagValue.LensSerialNumber: return new ExifString(ExifTag.LensSerialNumber); - case ExifTagValue.GDALMetadata: return new ExifString(ExifTag.GDALMetadata); - case ExifTagValue.GDALNoData: return new ExifString(ExifTag.GDALNoData); - case ExifTagValue.GPSLatitudeRef: return new ExifString(ExifTag.GPSLatitudeRef); - case ExifTagValue.GPSLongitudeRef: return new ExifString(ExifTag.GPSLongitudeRef); - case ExifTagValue.GPSSatellites: return new ExifString(ExifTag.GPSSatellites); - case ExifTagValue.GPSStatus: return new ExifString(ExifTag.GPSStatus); - case ExifTagValue.GPSMeasureMode: return new ExifString(ExifTag.GPSMeasureMode); - case ExifTagValue.GPSSpeedRef: return new ExifString(ExifTag.GPSSpeedRef); - case ExifTagValue.GPSTrackRef: return new ExifString(ExifTag.GPSTrackRef); - case ExifTagValue.GPSImgDirectionRef: return new ExifString(ExifTag.GPSImgDirectionRef); - case ExifTagValue.GPSMapDatum: return new ExifString(ExifTag.GPSMapDatum); - case ExifTagValue.GPSDestLatitudeRef: return new ExifString(ExifTag.GPSDestLatitudeRef); - case ExifTagValue.GPSDestLongitudeRef: return new ExifString(ExifTag.GPSDestLongitudeRef); - case ExifTagValue.GPSDestBearingRef: return new ExifString(ExifTag.GPSDestBearingRef); - case ExifTagValue.GPSDestDistanceRef: return new ExifString(ExifTag.GPSDestDistanceRef); - case ExifTagValue.GPSDateStamp: return new ExifString(ExifTag.GPSDateStamp); + case ExifTagValue.ImageDescription: + return new ExifString(ExifTag.ImageDescription); + case ExifTagValue.Make: + return new ExifString(ExifTag.Make); + case ExifTagValue.Model: + return new ExifString(ExifTag.Model); + case ExifTagValue.Software: + return new ExifString(ExifTag.Software); + case ExifTagValue.DateTime: + return new ExifString(ExifTag.DateTime); + case ExifTagValue.Artist: + return new ExifString(ExifTag.Artist); + case ExifTagValue.HostComputer: + return new ExifString(ExifTag.HostComputer); + case ExifTagValue.Copyright: + return new ExifString(ExifTag.Copyright); + case ExifTagValue.DocumentName: + return new ExifString(ExifTag.DocumentName); + case ExifTagValue.PageName: + return new ExifString(ExifTag.PageName); + case ExifTagValue.InkNames: + return new ExifString(ExifTag.InkNames); + case ExifTagValue.TargetPrinter: + return new ExifString(ExifTag.TargetPrinter); + case ExifTagValue.ImageID: + return new ExifString(ExifTag.ImageID); + case ExifTagValue.MDLabName: + return new ExifString(ExifTag.MDLabName); + case ExifTagValue.MDSampleInfo: + return new ExifString(ExifTag.MDSampleInfo); + case ExifTagValue.MDPrepDate: + return new ExifString(ExifTag.MDPrepDate); + case ExifTagValue.MDPrepTime: + return new ExifString(ExifTag.MDPrepTime); + case ExifTagValue.MDFileUnits: + return new ExifString(ExifTag.MDFileUnits); + case ExifTagValue.SEMInfo: + return new ExifString(ExifTag.SEMInfo); + case ExifTagValue.SpectralSensitivity: + return new ExifString(ExifTag.SpectralSensitivity); + case ExifTagValue.DateTimeOriginal: + return new ExifString(ExifTag.DateTimeOriginal); + case ExifTagValue.DateTimeDigitized: + return new ExifString(ExifTag.DateTimeDigitized); + case ExifTagValue.SubsecTime: + return new ExifString(ExifTag.SubsecTime); + case ExifTagValue.SubsecTimeOriginal: + return new ExifString(ExifTag.SubsecTimeOriginal); + case ExifTagValue.SubsecTimeDigitized: + return new ExifString(ExifTag.SubsecTimeDigitized); + case ExifTagValue.RelatedSoundFile: + return new ExifString(ExifTag.RelatedSoundFile); + case ExifTagValue.FaxSubaddress: + return new ExifString(ExifTag.FaxSubaddress); + case ExifTagValue.OffsetTime: + return new ExifString(ExifTag.OffsetTime); + case ExifTagValue.OffsetTimeOriginal: + return new ExifString(ExifTag.OffsetTimeOriginal); + case ExifTagValue.OffsetTimeDigitized: + return new ExifString(ExifTag.OffsetTimeDigitized); + case ExifTagValue.SecurityClassification: + return new ExifString(ExifTag.SecurityClassification); + case ExifTagValue.ImageHistory: + return new ExifString(ExifTag.ImageHistory); + case ExifTagValue.ImageUniqueID: + return new ExifString(ExifTag.ImageUniqueID); + case ExifTagValue.OwnerName: + return new ExifString(ExifTag.OwnerName); + case ExifTagValue.SerialNumber: + return new ExifString(ExifTag.SerialNumber); + case ExifTagValue.LensMake: + return new ExifString(ExifTag.LensMake); + case ExifTagValue.LensModel: + return new ExifString(ExifTag.LensModel); + case ExifTagValue.LensSerialNumber: + return new ExifString(ExifTag.LensSerialNumber); + case ExifTagValue.GDALMetadata: + return new ExifString(ExifTag.GDALMetadata); + case ExifTagValue.GDALNoData: + return new ExifString(ExifTag.GDALNoData); + case ExifTagValue.GPSLatitudeRef: + return new ExifString(ExifTag.GPSLatitudeRef); + case ExifTagValue.GPSLongitudeRef: + return new ExifString(ExifTag.GPSLongitudeRef); + case ExifTagValue.GPSSatellites: + return new ExifString(ExifTag.GPSSatellites); + case ExifTagValue.GPSStatus: + return new ExifString(ExifTag.GPSStatus); + case ExifTagValue.GPSMeasureMode: + return new ExifString(ExifTag.GPSMeasureMode); + case ExifTagValue.GPSSpeedRef: + return new ExifString(ExifTag.GPSSpeedRef); + case ExifTagValue.GPSTrackRef: + return new ExifString(ExifTag.GPSTrackRef); + case ExifTagValue.GPSImgDirectionRef: + return new ExifString(ExifTag.GPSImgDirectionRef); + case ExifTagValue.GPSMapDatum: + return new ExifString(ExifTag.GPSMapDatum); + case ExifTagValue.GPSDestLatitudeRef: + return new ExifString(ExifTag.GPSDestLatitudeRef); + case ExifTagValue.GPSDestLongitudeRef: + return new ExifString(ExifTag.GPSDestLongitudeRef); + case ExifTagValue.GPSDestBearingRef: + return new ExifString(ExifTag.GPSDestBearingRef); + case ExifTagValue.GPSDestDistanceRef: + return new ExifString(ExifTag.GPSDestDistanceRef); + case ExifTagValue.GPSDateStamp: + return new ExifString(ExifTag.GPSDateStamp); - case ExifTagValue.FileSource: return new ExifByte(ExifTag.FileSource, ExifDataType.Undefined); - case ExifTagValue.SceneType: return new ExifByte(ExifTag.SceneType, ExifDataType.Undefined); + case ExifTagValue.FileSource: + return new ExifByte(ExifTag.FileSource, ExifDataType.Undefined); + case ExifTagValue.SceneType: + return new ExifByte(ExifTag.SceneType, ExifDataType.Undefined); - case ExifTagValue.JPEGTables: return new ExifByteArray(ExifTag.JPEGTables, ExifDataType.Undefined); - case ExifTagValue.OECF: return new ExifByteArray(ExifTag.OECF, ExifDataType.Undefined); - case ExifTagValue.ExifVersion: return new ExifByteArray(ExifTag.ExifVersion, ExifDataType.Undefined); - case ExifTagValue.ComponentsConfiguration: return new ExifByteArray(ExifTag.ComponentsConfiguration, ExifDataType.Undefined); - case ExifTagValue.MakerNote: return new ExifByteArray(ExifTag.MakerNote, ExifDataType.Undefined); - case ExifTagValue.UserComment: return new ExifByteArray(ExifTag.UserComment, ExifDataType.Undefined); - case ExifTagValue.FlashpixVersion: return new ExifByteArray(ExifTag.FlashpixVersion, ExifDataType.Undefined); - case ExifTagValue.SpatialFrequencyResponse: return new ExifByteArray(ExifTag.SpatialFrequencyResponse, ExifDataType.Undefined); - case ExifTagValue.SpatialFrequencyResponse2: return new ExifByteArray(ExifTag.SpatialFrequencyResponse2, ExifDataType.Undefined); - case ExifTagValue.Noise: return new ExifByteArray(ExifTag.Noise, ExifDataType.Undefined); - case ExifTagValue.CFAPattern: return new ExifByteArray(ExifTag.CFAPattern, ExifDataType.Undefined); - case ExifTagValue.DeviceSettingDescription: return new ExifByteArray(ExifTag.DeviceSettingDescription, ExifDataType.Undefined); - case ExifTagValue.ImageSourceData: return new ExifByteArray(ExifTag.ImageSourceData, ExifDataType.Undefined); - case ExifTagValue.GPSProcessingMethod: return new ExifByteArray(ExifTag.GPSProcessingMethod, ExifDataType.Undefined); - case ExifTagValue.GPSAreaInformation: return new ExifByteArray(ExifTag.GPSAreaInformation, ExifDataType.Undefined); + case ExifTagValue.JPEGTables: + return new ExifByteArray(ExifTag.JPEGTables, ExifDataType.Undefined); + case ExifTagValue.OECF: + return new ExifByteArray(ExifTag.OECF, ExifDataType.Undefined); + case ExifTagValue.ExifVersion: + return new ExifByteArray(ExifTag.ExifVersion, ExifDataType.Undefined); + case ExifTagValue.ComponentsConfiguration: + return new ExifByteArray(ExifTag.ComponentsConfiguration, ExifDataType.Undefined); + case ExifTagValue.MakerNote: + return new ExifByteArray(ExifTag.MakerNote, ExifDataType.Undefined); + case ExifTagValue.FlashpixVersion: + return new ExifByteArray(ExifTag.FlashpixVersion, ExifDataType.Undefined); + case ExifTagValue.SpatialFrequencyResponse: + return new ExifByteArray(ExifTag.SpatialFrequencyResponse, ExifDataType.Undefined); + case ExifTagValue.SpatialFrequencyResponse2: + return new ExifByteArray(ExifTag.SpatialFrequencyResponse2, ExifDataType.Undefined); + case ExifTagValue.Noise: + return new ExifByteArray(ExifTag.Noise, ExifDataType.Undefined); + case ExifTagValue.CFAPattern: + return new ExifByteArray(ExifTag.CFAPattern, ExifDataType.Undefined); + case ExifTagValue.DeviceSettingDescription: + return new ExifByteArray(ExifTag.DeviceSettingDescription, ExifDataType.Undefined); + case ExifTagValue.ImageSourceData: + return new ExifByteArray(ExifTag.ImageSourceData, ExifDataType.Undefined); - default: return null; + case ExifTagValue.XPTitle: + return new ExifUcs2String(ExifTag.XPTitle); + case ExifTagValue.XPComment: + return new ExifUcs2String(ExifTag.XPComment); + case ExifTagValue.XPAuthor: + return new ExifUcs2String(ExifTag.XPAuthor); + case ExifTagValue.XPKeywords: + return new ExifUcs2String(ExifTag.XPKeywords); + case ExifTagValue.XPSubject: + return new ExifUcs2String(ExifTag.XPSubject); + + case ExifTagValue.UserComment: + return new ExifEncodedString(ExifTag.UserComment); + case ExifTagValue.GPSProcessingMethod: + return new ExifEncodedString(ExifTag.GPSProcessingMethod); + case ExifTagValue.GPSAreaInformation: + return new ExifEncodedString(ExifTag.GPSAreaInformation); + + default: + return null; } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs index 6c5219b3a5..c9f9d4327a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs @@ -28,42 +28,42 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// protected override void BeforeImageApply() { - OrientationMode orientation = GetExifOrientation(this.Source); + ushort orientation = GetExifOrientation(this.Source); Size size = this.SourceRectangle.Size; switch (orientation) { - case OrientationMode.TopRight: + case ExifOrientationMode.TopRight: new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle); break; - case OrientationMode.BottomRight: + case ExifOrientationMode.BottomRight: new RotateProcessor((int)RotateMode.Rotate180, size).Execute(this.Configuration, this.Source, this.SourceRectangle); break; - case OrientationMode.BottomLeft: + case ExifOrientationMode.BottomLeft: new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle); break; - case OrientationMode.LeftTop: + case ExifOrientationMode.LeftTop: new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle); break; - case OrientationMode.RightTop: + case ExifOrientationMode.RightTop: new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle); break; - case OrientationMode.RightBottom: + case ExifOrientationMode.RightBottom: new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle); break; - case OrientationMode.LeftBottom: + case ExifOrientationMode.LeftBottom: new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle); break; - case OrientationMode.Unknown: - case OrientationMode.TopLeft: + case ExifOrientationMode.Unknown: + case ExifOrientationMode.TopLeft: default: break; } @@ -81,32 +81,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Returns the current EXIF orientation /// /// The image to auto rotate. - /// The - private static OrientationMode GetExifOrientation(Image source) + /// The + private static ushort GetExifOrientation(Image source) { if (source.Metadata.ExifProfile is null) { - return OrientationMode.Unknown; + return ExifOrientationMode.Unknown; } IExifValue value = source.Metadata.ExifProfile.GetValue(ExifTag.Orientation); if (value is null) { - return OrientationMode.Unknown; + return ExifOrientationMode.Unknown; } - OrientationMode orientation; + ushort orientation; if (value.DataType == ExifDataType.Short) { - orientation = (OrientationMode)value.Value; + orientation = value.Value; } else { - orientation = (OrientationMode)Convert.ToUInt16(value.Value); + orientation = Convert.ToUInt16(value.Value); source.Metadata.ExifProfile.RemoveValue(ExifTag.Orientation); } - source.Metadata.ExifProfile.SetValue(ExifTag.Orientation, (ushort)OrientationMode.TopLeft); + source.Metadata.ExifProfile.SetValue(ExifTag.Orientation, ExifOrientationMode.TopLeft); return orientation; } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs index 9db666c374..988c056608 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs @@ -39,10 +39,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg using var memoryStream = new MemoryStream(this.jpegBytes); using var bufferedStream = new BufferedReadStream(Configuration.Default, memoryStream); - var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder { IgnoreMetadata = true }); + using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder { IgnoreMetadata = true }); var scanDecoder = new HuffmanScanDecoder(bufferedStream, new NoopSpectralConverter(), cancellationToken: default); decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default); - decoder.Dispose(); } // We want to test only stream parsing and scan decoding, we don't need to convert spectral data to actual pixels diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 6bf606ac90..c8ecdb717b 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -247,5 +247,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif "Disco") .Dispose(); } + + // https://github.com/SixLabors/ImageSharp/issues/1962 + [Theory] + [WithFile(TestImages.Gif.Issues.Issue1962NoColorTable, PixelTypes.Rgba32)] + public void Issue1962(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.DebugSave(provider); + + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs index db5169a045..70cbc3af72 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -42,6 +42,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // High depth images TestImages.Jpeg.Baseline.Testorig12bit, + + // Grayscale jpeg with 2x2 sampling factors (not a usual thing to encounter in the wild) + TestImages.Jpeg.Baseline.GrayscaleSampling2x2, }; public static string[] ProgressiveTestJpegs = diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 53c81631de..840cc9f685 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Runtime.CompilerServices; +using System.Text; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Metadata; @@ -300,5 +301,72 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg }); Assert.Null(ex); } + + [Fact] + public void EncodedStringTags_WriteAndRead() + { + using var memoryStream = new MemoryStream(); + using (var image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora))) + { + var exif = new ExifProfile(); + + exif.SetValue(ExifTag.GPSDateStamp, "2022-01-06"); + + exif.SetValue(ExifTag.XPTitle, "A bit of test metadata for image title"); + exif.SetValue(ExifTag.XPComment, "A bit of test metadata for image comment"); + exif.SetValue(ExifTag.XPAuthor, "Dan Petitt"); + exif.SetValue(ExifTag.XPKeywords, "Keyword1;Keyword2"); + exif.SetValue(ExifTag.XPSubject, "This is a subject"); + + // exif.SetValue(ExifTag.UserComment, new EncodedString(EncodedString.CharacterCode.JIS, "ビッ")); + exif.SetValue(ExifTag.UserComment, new EncodedString(EncodedString.CharacterCode.JIS, "eng comment text (JIS)")); + + exif.SetValue(ExifTag.GPSProcessingMethod, new EncodedString(EncodedString.CharacterCode.ASCII, "GPS processing method (ASCII)")); + exif.SetValue(ExifTag.GPSAreaInformation, new EncodedString(EncodedString.CharacterCode.Unicode, "GPS area info (Unicode)")); + + image.Metadata.ExifProfile = exif; + + image.Save(memoryStream, new JpegEncoder()); + } + + memoryStream.Seek(0, SeekOrigin.Begin); + using (var image = Image.Load(memoryStream)) + { + ExifProfile exif = image.Metadata.ExifProfile; + VerifyEncodedStrings(exif); + } + } + + [Fact] + public void EncodedStringTags_Read() + { + using (var image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora_EncodedStrings))) + { + ExifProfile exif = image.Metadata.ExifProfile; + VerifyEncodedStrings(exif); + } + } + + private static void VerifyEncodedStrings(ExifProfile exif) + { + Assert.NotNull(exif); + + Assert.Equal("2022-01-06", exif.GetValue(ExifTag.GPSDateStamp).Value); + + Assert.Equal("A bit of test metadata for image title", exif.GetValue(ExifTag.XPTitle).Value); + Assert.Equal("A bit of test metadata for image comment", exif.GetValue(ExifTag.XPComment).Value); + Assert.Equal("Dan Petitt", exif.GetValue(ExifTag.XPAuthor).Value); + Assert.Equal("Keyword1;Keyword2", exif.GetValue(ExifTag.XPKeywords).Value); + Assert.Equal("This is a subject", exif.GetValue(ExifTag.XPSubject).Value); + + Assert.Equal("eng comment text (JIS)", exif.GetValue(ExifTag.UserComment).Value.Text); + Assert.Equal(EncodedString.CharacterCode.JIS, exif.GetValue(ExifTag.UserComment).Value.Code); + + Assert.Equal("GPS processing method (ASCII)", exif.GetValue(ExifTag.GPSProcessingMethod).Value.Text); + Assert.Equal(EncodedString.CharacterCode.ASCII, exif.GetValue(ExifTag.GPSProcessingMethod).Value.Code); + + Assert.Equal("GPS area info (Unicode)", (string)exif.GetValue(ExifTag.GPSAreaInformation).Value); + Assert.Equal(EncodedString.CharacterCode.Unicode, exif.GetValue(ExifTag.GPSAreaInformation).Value.Code); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 35113f14ff..3833b419c4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // Calculating data from ImageSharp byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; - var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); using var ms = new MemoryStream(sourceBytes); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // Calculating data from ImageSharp byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; - var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); using var ms = new MemoryStream(sourceBytes); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs index 0071c623c6..27240831c3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // Decoding using var converter = new SpectralConverter(Configuration.Default); - var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); var scanDecoder = new HuffmanScanDecoder(bufferedStream, converter, cancellationToken: default); decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default); diff --git a/tests/ImageSharp.Tests/Memory/Allocators/MemoryDiagnosticsTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/MemoryDiagnosticsTests.cs new file mode 100644 index 0000000000..5fab655cb3 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/MemoryDiagnosticsTests.cs @@ -0,0 +1,122 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Diagnostics; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public class MemoryDiagnosticsTests + { + private const int OneMb = 1 << 20; + + private static MemoryAllocator Allocator => Configuration.Default.MemoryAllocator; + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void PerfectCleanup_NoLeaksReported(bool isGroupOuter) + { + RemoteExecutor.Invoke(RunTest, isGroupOuter.ToString()).Dispose(); + + static void RunTest(string isGroupInner) + { + bool isGroup = bool.Parse(isGroupInner); + int leakCounter = 0; + MemoryDiagnostics.UndisposedAllocation += _ => Interlocked.Increment(ref leakCounter); + + List buffers = new(); + + Assert.Equal(0, MemoryDiagnostics.TotalUndisposedAllocationCount); + for (int length = 1024; length <= 64 * OneMb; length *= 2) + { + long cntBefore = MemoryDiagnostics.TotalUndisposedAllocationCount; + IDisposable buffer = isGroup ? + Allocator.AllocateGroup(length, 1024) : + Allocator.Allocate(length); + buffers.Add(buffer); + long cntAfter = MemoryDiagnostics.TotalUndisposedAllocationCount; + Assert.True(cntAfter > cntBefore); + } + + foreach (IDisposable buffer in buffers) + { + long cntBefore = MemoryDiagnostics.TotalUndisposedAllocationCount; + buffer.Dispose(); + long cntAfter = MemoryDiagnostics.TotalUndisposedAllocationCount; + Assert.True(cntAfter < cntBefore); + } + + Assert.Equal(0, MemoryDiagnostics.TotalUndisposedAllocationCount); + Assert.Equal(0, leakCounter); + } + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void MissingCleanup_LeaksAreReported(bool isGroupOuter, bool subscribeLeakHandleOuter) + { + RemoteExecutor.Invoke(RunTest, isGroupOuter.ToString(), subscribeLeakHandleOuter.ToString()).Dispose(); + + static void RunTest(string isGroupInner, string subscribeLeakHandleInner) + { + bool isGroup = bool.Parse(isGroupInner); + bool subscribeLeakHandle = bool.Parse(subscribeLeakHandleInner); + int leakCounter = 0; + bool stackTraceOk = true; + if (subscribeLeakHandle) + { + MemoryDiagnostics.UndisposedAllocation += stackTrace => + { + Interlocked.Increment(ref leakCounter); + stackTraceOk &= stackTrace.Contains(nameof(RunTest)) && stackTrace.Contains(nameof(AllocateAndForget)); + Assert.Contains(nameof(AllocateAndForget), stackTrace); + }; + } + + Assert.Equal(0, MemoryDiagnostics.TotalUndisposedAllocationCount); + for (int length = 1024; length <= 64 * OneMb; length *= 2) + { + long cntBefore = MemoryDiagnostics.TotalUndisposedAllocationCount; + AllocateAndForget(length, isGroup); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + long cntAfter = MemoryDiagnostics.TotalUndisposedAllocationCount; + Assert.True(cntAfter > cntBefore); + } + + if (subscribeLeakHandle) + { + // Make sure at least some of the leak callbacks have time to complete on the ThreadPool + Thread.Sleep(200); + Assert.True(leakCounter > 3, $"leakCounter did not count enough leaks ({leakCounter} only)"); + } + + Assert.True(stackTraceOk); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void AllocateAndForget(int length, bool isGroup) + { + if (isGroup) + { + _ = Allocator.AllocateGroup(length, 1024); + } + else + { + _ = Allocator.Allocate(length); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs index 7fb3b7b7bb..4b808e8630 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs @@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } - private class MockLifetimeGuard : RefCountedLifetimeGuard + private class MockLifetimeGuard : RefCountedMemoryLifetimeGuard { public int ReleaseInvocationCount { get; private set; } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index 7fc3ff6f19..a859852279 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -51,6 +51,9 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif { ExifTag.ImageDescription, "ImageDescription" }, { ExifTag.ExposureTime, new Rational(1.0 / 1600.0) }, { ExifTag.Model, "Model" }, + { ExifTag.XPAuthor, "The XPAuthor text" }, + { ExifTag.UserComment, new EncodedString(EncodedString.CharacterCode.Unicode, "The Unicode text") }, + { ExifTag.GPSAreaInformation, new EncodedString("Default constructor text (GPSAreaInformation)") }, }; [Theory] @@ -504,7 +507,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif public void IfdStructure() { var exif = new ExifProfile(); - exif.SetValue(ExifTag.XPAuthor, Encoding.GetEncoding("UCS-2").GetBytes("Dan Petitt")); + exif.SetValue(ExifTag.XPAuthor, "Dan Petitt"); Span actualBytes = exif.ToByteArray(); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs index a5ca115d49..aaad4e2306 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Text; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using Xunit; @@ -23,11 +25,6 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values { ExifTag.XMP }, { ExifTag.CFAPattern2 }, { ExifTag.TIFFEPStandardID }, - { ExifTag.XPTitle }, - { ExifTag.XPComment }, - { ExifTag.XPAuthor }, - { ExifTag.XPKeywords }, - { ExifTag.XPSubject }, { ExifTag.GPSVersionID }, }; @@ -295,7 +292,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values { ExifTag.GPSDestLongitudeRef }, { ExifTag.GPSDestBearingRef }, { ExifTag.GPSDestDistanceRef }, - { ExifTag.GPSDateStamp } + { ExifTag.GPSDateStamp }, }; public static TheoryData UndefinedTags => new TheoryData @@ -311,7 +308,6 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values { ExifTag.ExifVersion }, { ExifTag.ComponentsConfiguration }, { ExifTag.MakerNote }, - { ExifTag.UserComment }, { ExifTag.FlashpixVersion }, { ExifTag.SpatialFrequencyResponse }, { ExifTag.SpatialFrequencyResponse2 }, @@ -319,10 +315,24 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values { ExifTag.CFAPattern }, { ExifTag.DeviceSettingDescription }, { ExifTag.ImageSourceData }, + }; + + public static TheoryData EncodedStringTags => new TheoryData + { + { ExifTag.UserComment }, { ExifTag.GPSProcessingMethod }, { ExifTag.GPSAreaInformation } }; + public static TheoryData Ucs2StringTags => new TheoryData + { + { ExifTag.XPTitle }, + { ExifTag.XPComment }, + { ExifTag.XPAuthor }, + { ExifTag.XPKeywords }, + { ExifTag.XPSubject }, + }; + [Theory] [MemberData(nameof(ByteTags))] public void ExifByteTests(ExifTag tag) @@ -593,5 +603,46 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values var typed = (ExifByteArray)value; Assert.Equal(expected, typed.Value); } + + [Theory] + [MemberData(nameof(EncodedStringTags))] + public void ExifEncodedStringTests(ExifTag tag) + { + foreach (object code in Enum.GetValues(typeof(EncodedString.CharacterCode))) + { + var charCode = (EncodedString.CharacterCode)code; + + Assert.Equal(ExifEncodedStringHelpers.CharacterCodeBytesLength, ExifEncodedStringHelpers.GetCodeBytes(charCode).Length); + + const string expectedText = "test string"; + var expected = new EncodedString(charCode, expectedText); + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(123)); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifEncodedString)value; + Assert.Equal(expected, typed.Value); + Assert.Equal(expectedText, (string)typed.Value); + Assert.Equal(charCode, typed.Value.Code); + } + } + + [Theory] + [MemberData(nameof(Ucs2StringTags))] + public void ExifUcs2StringTests(ExifTag tag) + { + const string expected = "Dan Petitt"; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(123)); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifUcs2String)value; + Assert.Equal(expected, typed.Value); + + Assert.True(value.TrySetValue(Encoding.GetEncoding("UCS-2").GetBytes(expected))); + Assert.Equal(expected, typed.Value); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs index 38fde5060a..597c02dfc2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs @@ -18,42 +18,41 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public const string FlipTestFile = TestImages.Bmp.F; public static readonly TheoryData InvalidOrientationValues - = new TheoryData - { - { ExifDataType.Byte, new byte[] { 1 } }, - { ExifDataType.SignedByte, new byte[] { 2 } }, - { ExifDataType.SignedShort, BitConverter.GetBytes((short)3) }, - { ExifDataType.Long, BitConverter.GetBytes(4U) }, - { ExifDataType.SignedLong, BitConverter.GetBytes(5) } - }; + = new() + { + { ExifDataType.Byte, new byte[] { 1 } }, + { ExifDataType.SignedByte, new byte[] { 2 } }, + { ExifDataType.SignedShort, BitConverter.GetBytes((short)3) }, + { ExifDataType.Long, BitConverter.GetBytes(4U) }, + { ExifDataType.SignedLong, BitConverter.GetBytes(5) } + }; - public static readonly TheoryData ExifOrientationValues = new TheoryData - { - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8 - }; + public static readonly TheoryData ExifOrientationValues + = new() + { + ExifOrientationMode.Unknown, + ExifOrientationMode.TopLeft, + ExifOrientationMode.TopRight, + ExifOrientationMode.BottomRight, + ExifOrientationMode.BottomLeft, + ExifOrientationMode.LeftTop, + ExifOrientationMode.RightTop, + ExifOrientationMode.RightBottom, + ExifOrientationMode.LeftBottom + }; [Theory] [WithFile(FlipTestFile, nameof(ExifOrientationValues), PixelTypes.Rgba32)] public void AutoOrient_WorksForAllExifOrientations(TestImageProvider provider, ushort orientation) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - image.Metadata.ExifProfile = new ExifProfile(); - image.Metadata.ExifProfile.SetValue(ExifTag.Orientation, orientation); + using Image image = provider.GetImage(); + image.Metadata.ExifProfile = new ExifProfile(); + image.Metadata.ExifProfile.SetValue(ExifTag.Orientation, orientation); - image.Mutate(x => x.AutoOrient()); - image.DebugSave(provider, orientation, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput(provider, orientation, appendPixelTypeToFileName: false); - } + image.Mutate(x => x.AutoOrient()); + image.DebugSave(provider, orientation, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput(provider, orientation, appendPixelTypeToFileName: false); } [Theory] @@ -76,19 +75,17 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms // Change the number of components bytes[20] = 1; - var orientationCodeData = new byte[8]; + byte[] orientationCodeData = new byte[8]; Array.Copy(orientation, orientationCodeData, orientation.Length); ulong orientationCode = BitConverter.ToUInt64(orientationCodeData, 0); - using (Image image = provider.GetImage()) - using (Image reference = image.Clone()) - { - image.Metadata.ExifProfile = new ExifProfile(bytes); - image.Mutate(x => x.AutoOrient()); - image.DebugSave(provider, $"{dataType}-{orientationCode}", appendPixelTypeToFileName: false); - ImageComparer.Exact.VerifySimilarity(image, reference); - } + using Image image = provider.GetImage(); + using Image reference = image.Clone(); + image.Metadata.ExifProfile = new ExifProfile(bytes); + image.Mutate(x => x.AutoOrient()); + image.DebugSave(provider, $"{dataType}-{orientationCode}", appendPixelTypeToFileName: false); + ImageComparer.Exact.VerifySimilarity(image, reference); } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index a73d262433..8b943194a5 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -192,6 +192,7 @@ namespace SixLabors.ImageSharp.Tests public const string Exif = "Jpg/baseline/exif.jpg"; public const string Floorplan = "Jpg/baseline/Floorplan.jpg"; public const string Calliphora = "Jpg/baseline/Calliphora.jpg"; + public const string Calliphora_EncodedStrings = "Jpg/baseline/Calliphora_encoded_strings.jpg"; public const string Ycck = "Jpg/baseline/ycck.jpg"; public const string Turtle420 = "Jpg/baseline/turtle.jpg"; public const string GammaDalaiLamaGray = "Jpg/baseline/gamma_dalai_lama_gray.jpg"; @@ -222,6 +223,7 @@ namespace SixLabors.ImageSharp.Tests public const string Winter444_Interleaved = "Jpg/baseline/winter444_interleaved.jpg"; public const string Metadata = "Jpg/baseline/Metadata-test-file.jpg"; public const string ExtendedXmp = "Jpg/baseline/extended-xmp.jpg"; + public const string GrayscaleSampling2x2 = "Jpg/baseline/grayscale_sampling22.jpg"; public static readonly string[] All = { @@ -449,6 +451,7 @@ namespace SixLabors.ImageSharp.Tests public const string Issue1505 = "Gif/issues/issue1505_argumentoutofrange.png"; public const string Issue1530 = "Gif/issues/issue1530.gif"; public const string InvalidColorIndex = "Gif/issues/issue1668_invalidcolorindex.gif"; + public const string Issue1962NoColorTable = "Gif/issues/issue1962_tiniest_gif_1st.gif"; } public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 }; diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1962_Rgba32_issue1962_tiniest_gif_1st.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1962_Rgba32_issue1962_tiniest_gif_1st.png new file mode 100644 index 0000000000..24f5e9c0cd --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1962_Rgba32_issue1962_tiniest_gif_1st.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f8c6d416f09671777934e57bc67fb52ccc97145dc6f1869e628d9ffd7d8f6e7 +size 119 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_grayscale_sampling22.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_grayscale_sampling22.png new file mode 100644 index 0000000000..b2c3effdda --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_grayscale_sampling22.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad22f28d20ea0ceda983a138b3bf9503ed836d779ed75a313f668329c910665e +size 168405 diff --git a/tests/Images/Input/Gif/issues/issue1962_tiniest_gif_1st.gif b/tests/Images/Input/Gif/issues/issue1962_tiniest_gif_1st.gif new file mode 100644 index 0000000000..a5bc432d8b --- /dev/null +++ b/tests/Images/Input/Gif/issues/issue1962_tiniest_gif_1st.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b7b8a4b411ddf8db9bacc2f3aabf406f8e4c0c087829b336ca331c40adfdff1 +size 26 diff --git a/tests/Images/Input/Jpg/baseline/Calliphora_encoded_strings.jpg b/tests/Images/Input/Jpg/baseline/Calliphora_encoded_strings.jpg new file mode 100644 index 0000000000..b652ed2e58 --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/Calliphora_encoded_strings.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59f76d2935a619d128a63d6bfcd5ce9feec492a7f5175327e47554c90b4ec242 +size 258081 diff --git a/tests/Images/Input/Jpg/baseline/grayscale_sampling22.jpg b/tests/Images/Input/Jpg/baseline/grayscale_sampling22.jpg new file mode 100644 index 0000000000..b861c68ab5 --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/grayscale_sampling22.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5cc8572082a54944d48b3e4f49e6c441871f6eb2b616fbbbfb025f20e0aeff5 +size 45066