Browse Source

Merge branch 'master' into bp/webpanimation

pull/1985/head
Brian Popow 5 years ago
committed by GitHub
parent
commit
71460c7662
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      .github/workflows/build-and-test.yml
  2. 83
      src/ImageSharp/Diagnostics/MemoryDiagnostics.cs
  3. 39
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  4. 61
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  5. 10
      src/ImageSharp/ImageSharp.csproj
  6. 56
      src/ImageSharp/Memory/Allocators/Internals/RefCountedLifetimeGuard.cs
  7. 79
      src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs
  8. 2
      src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs
  9. 4
      src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs
  10. 2
      src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs
  11. 2
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
  12. 30
      src/ImageSharp/Metadata/ImageMetadata.cs
  13. BIN
      src/ImageSharp/Metadata/Profiles/Exif/DC-X008-Translation-2019-E.pdf
  14. 4
      src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs
  15. 121
      src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs
  16. 19
      src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
  17. 21
      src/ImageSharp/Metadata/Profiles/Exif/ExifUcs2StringHelpers.cs
  18. 39
      src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs
  19. 25
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs
  20. 24
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.EncodedString.cs
  21. 34
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Ucs2String.cs
  22. 15
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs
  23. 94
      src/ImageSharp/Metadata/Profiles/Exif/Values/EncodedString.cs
  24. 55
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs
  25. 24
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifOrientationMode.cs
  26. 47
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifUcs2String.cs
  27. 806
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs
  28. 36
      src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs
  29. 3
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
  30. 12
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  31. 3
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
  32. 68
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  33. 4
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  34. 2
      tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs
  35. 122
      tests/ImageSharp.Tests/Memory/Allocators/MemoryDiagnosticsTests.cs
  36. 2
      tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs
  37. 5
      tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs
  38. 65
      tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs
  39. 71
      tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs
  40. 3
      tests/ImageSharp.Tests/TestImages.cs
  41. 3
      tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1962_Rgba32_issue1962_tiniest_gif_1st.png
  42. 3
      tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_grayscale_sampling22.png
  43. 3
      tests/Images/Input/Gif/issues/issue1962_tiniest_gif_1st.gif
  44. 3
      tests/Images/Input/Jpg/baseline/Calliphora_encoded_strings.jpg
  45. 3
      tests/Images/Input/Jpg/baseline/grayscale_sampling22.jpg

10
.github/workflows/build-and-test.yml

@ -109,12 +109,14 @@ jobs:
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }}
restore-keys: ${{ runner.os }}-nuget- restore-keys: ${{ runner.os }}-nuget-
- name: DotNet Setup Preview - name: DotNet Setup
if: ${{ matrix.options.sdk-preview == true }}
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: ${{ matrix.options.sdk }} dotnet-version: |
include-prerelease: true 6.0.x
5.0.x
3.1.x
2.1.x
- name: DotNet Build - name: DotNet Build
if: ${{ matrix.options.sdk-preview != true }} if: ${{ matrix.options.sdk-preview != true }}

83
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
{
/// <summary>
/// Represents the method to handle <see cref="MemoryDiagnostics.UndisposedAllocation"/>.
/// </summary>
public delegate void UndisposedAllocationDelegate(string allocationStackTrace);
/// <summary>
/// Utilities to track memory usage and detect memory leaks from not disposing ImageSharp objects.
/// </summary>
public static class MemoryDiagnostics
{
private static int totalUndisposedAllocationCount;
private static UndisposedAllocationDelegate undisposedAllocation;
private static int undisposedAllocationSubscriptionCounter;
private static readonly object SyncRoot = new();
/// <summary>
/// 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 <see cref="TotalUndisposedAllocationCount"/>.
/// </summary>
public static event UndisposedAllocationDelegate UndisposedAllocation
{
add
{
lock (SyncRoot)
{
undisposedAllocationSubscriptionCounter++;
undisposedAllocation += value;
}
}
remove
{
lock (SyncRoot)
{
undisposedAllocation -= value;
undisposedAllocationSubscriptionCounter--;
}
}
}
/// <summary>
/// Gets a value indicating the total number of memory resource objects leaked to the finalizer.
/// </summary>
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
}
}
}

39
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -376,7 +376,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
indices = this.Configuration.MemoryAllocator.Allocate2D<byte>(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean); indices = this.Configuration.MemoryAllocator.Allocate2D<byte>(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean);
this.ReadFrameIndices(indices); this.ReadFrameIndices(indices);
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>((localColorTable ?? this.globalColorTable).GetSpan()); Span<byte> rawColorTable = default;
if (localColorTable != null)
{
rawColorTable = localColorTable.GetSpan();
}
else if (this.globalColorTable != null)
{
rawColorTable = this.globalColorTable.GetSpan();
}
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>(rawColorTable);
this.ReadFrameColors(ref image, ref previousFrame, indices, colorTable, this.imageDescriptor); this.ReadFrameColors(ref image, ref previousFrame, indices, colorTable, this.imageDescriptor);
// Skip any remaining blocks // Skip any remaining blocks
@ -415,6 +425,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{ {
int imageWidth = this.logicalScreenDescriptor.Width; int imageWidth = this.logicalScreenDescriptor.Width;
int imageHeight = this.logicalScreenDescriptor.Height; int imageHeight = this.logicalScreenDescriptor.Height;
bool transFlag = this.graphicsControlExtension.TransparencyFlag;
ImageFrame<TPixel> prevFrame = null; ImageFrame<TPixel> prevFrame = null;
ImageFrame<TPixel> currentFrame = null; ImageFrame<TPixel> currentFrame = null;
@ -422,8 +433,15 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (previousFrame is null) if (previousFrame is null)
{ {
// This initializes the image to become fully transparent because the alpha channel is zero. if (!transFlag)
image = new Image<TPixel>(this.Configuration, imageWidth, imageHeight, this.metadata); {
image = new Image<TPixel>(this.Configuration, imageWidth, imageHeight, Color.Black.ToPixel<TPixel>(), this.metadata);
}
else
{
// This initializes the image to become fully transparent because the alpha channel is zero.
image = new Image<TPixel>(this.Configuration, imageWidth, imageHeight, this.metadata);
}
this.SetFrameMetadata(image.Frames.RootFrame.Metadata); this.SetFrameMetadata(image.Frames.RootFrame.Metadata);
@ -445,6 +463,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.RestoreToBackground(imageFrame); this.RestoreToBackground(imageFrame);
} }
if (colorTable.Length == 0)
{
return;
}
int interlacePass = 0; // The interlace pass int interlacePass = 0; // The interlace pass
int interlaceIncrement = 8; // The interlacing line increment int interlaceIncrement = 8; // The interlacing line increment
int interlaceY = 0; // The current interlaced line int interlaceY = 0; // The current interlaced line
@ -452,7 +475,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
int descriptorBottom = descriptorTop + descriptor.Height; int descriptorBottom = descriptorTop + descriptor.Height;
int descriptorLeft = descriptor.Left; int descriptorLeft = descriptor.Left;
int descriptorRight = descriptorLeft + descriptor.Width; int descriptorRight = descriptorLeft + descriptor.Width;
bool transFlag = this.graphicsControlExtension.TransparencyFlag;
byte transIndex = this.graphicsControlExtension.TransparencyIndex; byte transIndex = this.graphicsControlExtension.TransparencyIndex;
int colorTableMaxIdx = colorTable.Length - 1; int colorTableMaxIdx = colorTable.Length - 1;
@ -635,10 +657,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3;
this.gifMetadata.GlobalColorTableLength = globalColorTableLength; this.gifMetadata.GlobalColorTableLength = globalColorTableLength;
this.globalColorTable = this.MemoryAllocator.Allocate<byte>(globalColorTableLength, AllocationOptions.Clean); if (globalColorTableLength > 0)
{
this.globalColorTable = this.MemoryAllocator.Allocate<byte>(globalColorTableLength, AllocationOptions.Clean);
// Read the global color table data from the stream // Read the global color table data from the stream
stream.Read(this.globalColorTable.GetSpan()); stream.Read(this.globalColorTable.GetSpan());
}
} }
} }
} }

61
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs

@ -148,11 +148,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private void ParseBaselineData() private void ParseBaselineData()
{ {
if (this.componentsCount == this.frame.ComponentCount) if (this.componentsCount != 1)
{ {
this.ParseBaselineDataInterleaved(); this.ParseBaselineDataInterleaved();
this.spectralConverter.CommitConversion(); this.spectralConverter.CommitConversion();
} }
else if (this.frame.ComponentCount == 1)
{
this.ParseBaselineDataSingleComponent();
this.spectralConverter.CommitConversion();
}
else else
{ {
this.ParseBaselineDataNonInterleaved(); this.ParseBaselineDataNonInterleaved();
@ -161,7 +166,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private void ParseBaselineDataInterleaved() private void ParseBaselineDataInterleaved()
{ {
// Interleaved
int mcu = 0; int mcu = 0;
int mcusPerColumn = this.frame.McusPerColumn; int mcusPerColumn = this.frame.McusPerColumn;
int mcusPerLine = this.frame.McusPerLine; int mcusPerLine = this.frame.McusPerLine;
@ -198,7 +202,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
if (buffer.NoData) 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?) // so we need to decode what's left and return (or maybe throw?)
this.spectralConverter.ConvertStrideBaseline(); this.spectralConverter.ConvertStrideBaseline();
return; return;
@ -221,9 +225,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.HandleRestart(); this.HandleRestart();
} }
// convert from spectral to actual pixels via given converter // Convert from spectral to actual pixels via given converter
this.spectralConverter.ConvertStrideBaseline(); this.spectralConverter.ConvertStrideBaseline();
} }
// Stride conversion must be sealed for stride conversion approach
this.spectralConverter.CommitConversion();
} }
private void ParseBaselineDataNonInterleaved() 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<Block8x8> 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() private void CheckProgressiveData()
{ {
// Validate successive scan parameters. // Validate successive scan parameters.

10
src/ImageSharp/ImageSharp.csproj

@ -10,7 +10,7 @@
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression> <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<RepositoryUrl Condition="'$(RepositoryUrl)' == ''">https://github.com/SixLabors/ImageSharp/</RepositoryUrl> <RepositoryUrl Condition="'$(RepositoryUrl)' == ''">https://github.com/SixLabors/ImageSharp/</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
<PackageTags>Image Resize Crop Gif Jpg Jpeg Bitmap Pbm Png Tga NetCore</PackageTags> <PackageTags>Image Resize Crop Gif Jpg Jpeg Bitmap Pbm Png Tga Tiff WebP NetCore</PackageTags>
<Description>A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET</Description> <Description>A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET</Description>
<Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations> <Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations>
</PropertyGroup> </PropertyGroup>
@ -49,6 +49,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" /> <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard')) OR '$(TargetFramework)' == 'net472'"> <ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard')) OR '$(TargetFramework)' == 'net472'">
@ -57,13 +58,6 @@
<PackageReference Include="System.Memory" Version="4.5.4" /> <PackageReference Include="System.Memory" Version="4.5.4" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3'">
<PackageReference Include="System.IO.Compression" Version="4.3.0" />
<PackageReference Include="System.IO.UnmanagedMemoryStream" Version="4.3.0" />
<PackageReference Include="System.Threading.Tasks.Parallel" Version="4.3.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Formats\Jpeg\Components\Block8x8F.Generated.cs"> <Compile Update="Formats\Jpeg\Components\Block8x8F.Generated.cs">
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>

56
src/ImageSharp/Memory/Allocators/Internals/RefCountedLifetimeGuard.cs

@ -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
{
/// <summary>
/// Implements reference counting lifetime guard mechanism similar to the one provided by <see cref="SafeHandle"/>,
/// but without the restriction of the guarded object being a handle.
/// </summary>
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();
}
}

79
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
{
/// <summary>
/// Implements reference counting lifetime guard mechanism for memory resources
/// and maintains the value of <see cref="MemoryDiagnostics.TotalUndisposedAllocationCount"/>.
/// </summary>
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();
}
}
}
}
}

2
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; private byte[] array;

4
src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs

@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp.Memory.Internals
return buffer; 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 UniformUnmanagedMemoryPool pool;
private readonly UnmanagedMemoryHandle[] handles; private readonly UnmanagedMemoryHandle[] handles;

2
src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Memory.Internals
/// <summary> /// <summary>
/// Defines a strategy for managing unmanaged memory ownership. /// Defines a strategy for managing unmanaged memory ownership.
/// </summary> /// </summary>
internal abstract class UnmanagedBufferLifetimeGuard : RefCountedLifetimeGuard internal abstract class UnmanagedBufferLifetimeGuard : RefCountedMemoryLifetimeGuard
{ {
private UnmanagedMemoryHandle handle; private UnmanagedMemoryHandle handle;

2
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Memory
public sealed class Owned : MemoryGroup<T>, IEnumerable<Memory<T>> public sealed class Owned : MemoryGroup<T>, IEnumerable<Memory<T>>
{ {
private IMemoryOwner<T>[] memoryOwners; private IMemoryOwner<T>[] memoryOwners;
private RefCountedLifetimeGuard groupLifetimeGuard; private RefCountedMemoryLifetimeGuard groupLifetimeGuard;
public Owned(IMemoryOwner<T>[] memoryOwners, int bufferLength, long totalLength, bool swappable) public Owned(IMemoryOwner<T>[] memoryOwners, int bufferLength, long totalLength, bool swappable)
: base(bufferLength, totalLength) : base(bufferLength, totalLength)

30
src/ImageSharp/Metadata/ImageMetadata.cs

@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Metadata
/// <summary> /// <summary>
/// Gets or sets the resolution of the image in x- direction. /// 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 <see cref="ResolutionUnits"/> and should be an positive value.
/// </summary> /// </summary>
/// <value>The density of the image in x- direction.</value> /// <value>The density of the image in x- direction.</value>
public double HorizontalResolution public double HorizontalResolution
@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Metadata
/// <summary> /// <summary>
/// Gets or sets the resolution of the image in y- direction. /// 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 <see cref="ResolutionUnits"/> and should be an positive value.
/// </summary> /// </summary>
/// <value>The density of the image in y- direction.</value> /// <value>The density of the image in y- direction.</value>
public double VerticalResolution public double VerticalResolution
@ -108,10 +108,28 @@ namespace SixLabors.ImageSharp.Metadata
/// <summary> /// <summary>
/// Gets or sets unit of measure used when reporting resolution. /// Gets or sets unit of measure used when reporting resolution.
/// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity /// <list type="table">
/// 01 : Pixels per inch (2.54 cm) /// <listheader>
/// 02 : Pixels per centimeter /// <term>Value</term>
/// 03 : Pixels per meter /// <description>Unit</description>
/// </listheader>
/// <item>
/// <term>AspectRatio (00)</term>
/// <description>No units; width:height pixel aspect ratio = Ydensity:Xdensity</description>
/// </item>
/// <item>
/// <term>PixelsPerInch (01)</term>
/// <description>Pixels per inch (2.54 cm)</description>
/// </item>
/// <item>
/// <term>PixelsPerCentimeter (02)</term>
/// <description>Pixels per centimeter</description>
/// </item>
/// <item>
/// <term>PixelsPerMeter (03)</term>
/// <description>Pixels per meter (100 cm)</description>
/// </item>
/// </list>
/// </summary> /// </summary>
public PixelResolutionUnit ResolutionUnits { get; set; } public PixelResolutionUnit ResolutionUnits { get; set; }

BIN
src/ImageSharp/Metadata/Profiles/Exif/DC-X008-Translation-2019-E.pdf

Binary file not shown.

4
src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Text;
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{ {
@ -22,5 +23,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
0x00, 0x00,
0x2A 0x2A
}; };
// UTF-8 is better than ASCII, UTF-8 encodes the ASCII codes the same way
public static Encoding DefaultEncoding => Encoding.UTF8;
} }
} }

121
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<byte> AsciiCodeBytes => new byte[] { 0x41, 0x53, 0x43, 0x49, 0x49, 0, 0, 0 };
private static ReadOnlySpan<byte> JISCodeBytes => new byte[] { 0x4A, 0x49, 0x53, 0, 0, 0, 0, 0 };
private static ReadOnlySpan<byte> UnicodeCodeBytes => new byte[] { 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0 };
private static ReadOnlySpan<byte> 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<byte> 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<byte> 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<byte> 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<byte> destination)
{
fixed (char* c = value)
{
fixed (byte* b = destination)
{
return encoding.GetBytes(c, value.Length, b, destination.Length);
}
}
}
private static bool TryDetect(ReadOnlySpan<byte> 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;
}
}
}

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

@ -241,9 +241,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
return result; return result;
} }
private byte ConvertToByte(ReadOnlySpan<byte> buffer) => buffer[0]; private static string ConvertToString(Encoding encoding, ReadOnlySpan<byte> buffer)
private string ConvertToString(ReadOnlySpan<byte> buffer)
{ {
int nullCharIndex = buffer.IndexOf((byte)0); int nullCharIndex = buffer.IndexOf((byte)0);
@ -252,9 +250,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
buffer = buffer.Slice(0, nullCharIndex); buffer = buffer.Slice(0, nullCharIndex);
} }
return Encoding.UTF8.GetString(buffer); return encoding.GetString(buffer);
} }
private byte ConvertToByte(ReadOnlySpan<byte> buffer) => buffer[0];
private object ConvertValue(ExifDataType dataType, ReadOnlySpan<byte> buffer, bool isArray) private object ConvertValue(ExifDataType dataType, ReadOnlySpan<byte> buffer, bool isArray)
{ {
if (buffer.Length == 0) if (buffer.Length == 0)
@ -267,8 +267,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
case ExifDataType.Unknown: case ExifDataType.Unknown:
return null; return null;
case ExifDataType.Ascii: case ExifDataType.Ascii:
return this.ConvertToString(buffer); return ConvertToString(ExifConstants.DefaultEncoding, buffer);
case ExifDataType.Byte: case ExifDataType.Byte:
case ExifDataType.Undefined:
if (!isArray) if (!isArray)
{ {
return this.ConvertToByte(buffer); return this.ConvertToByte(buffer);
@ -354,13 +355,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
} }
return ToArray(dataType, buffer, this.ConvertToUInt64); return ToArray(dataType, buffer, this.ConvertToUInt64);
case ExifDataType.Undefined:
if (!isArray)
{
return this.ConvertToByte(buffer);
}
return buffer.ToArray();
default: default:
throw new NotSupportedException($"Data type {dataType} is not supported."); 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. // 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. // Again, the old datatypes TIFF_IFD, and the hardly recommendable TIFF_LONG, are still valid, too.
// https://www.awaresystems.be/imaging/tiff/bigtiff.html // https://www.awaresystems.be/imaging/tiff/bigtiff.html
ExifValue exifValue = null; ExifValue exifValue;
switch (tag) switch (tag)
{ {
case ExifTagValue.StripOffsets: case ExifTagValue.StripOffsets:

21
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<byte> destination) => ExifEncodedStringHelpers.Write(Ucs2Encoding, value, destination);
}
}

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

@ -4,7 +4,6 @@
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{ {
@ -274,9 +273,19 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{ {
object value = exifValue.GetValue(); 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) 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) if (value is Array arrayValue)
@ -289,11 +298,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
private static int WriteArray(IExifValue value, Span<byte> destination, int offset) private static int WriteArray(IExifValue value, Span<byte> destination, int offset)
{ {
if (value.DataType == ExifDataType.Ascii)
{
return WriteValue(ExifDataType.Ascii, value.GetValue(), destination, offset);
}
int newOffset = offset; int newOffset = offset;
foreach (object obj in (Array)value.GetValue()) foreach (object obj in (Array)value.GetValue())
{ {
@ -378,7 +382,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
switch (dataType) switch (dataType)
{ {
case ExifDataType.Ascii: case ExifDataType.Ascii:
offset = Write(Encoding.UTF8.GetBytes((string)value), destination, offset); offset = Write(ExifConstants.DefaultEncoding.GetBytes((string)value), destination, offset);
destination[offset] = 0; destination[offset] = 0;
return offset + 1; return offset + 1;
case ExifDataType.Byte: case ExifDataType.Byte:
@ -425,14 +429,25 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
} }
} }
internal static int WriteValue(IExifValue value, Span<byte> destination, int offset) internal static int WriteValue(IExifValue exifValue, Span<byte> 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);
} }
} }
} }

25
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs

@ -41,31 +41,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary> /// </summary>
public static ExifTag<byte[]> TIFFEPStandardID => new ExifTag<byte[]>(ExifTagValue.TIFFEPStandardID); public static ExifTag<byte[]> TIFFEPStandardID => new ExifTag<byte[]>(ExifTagValue.TIFFEPStandardID);
/// <summary>
/// Gets the XPTitle exif tag.
/// </summary>
public static ExifTag<byte[]> XPTitle => new ExifTag<byte[]>(ExifTagValue.XPTitle);
/// <summary>
/// Gets the XPComment exif tag.
/// </summary>
public static ExifTag<byte[]> XPComment => new ExifTag<byte[]>(ExifTagValue.XPComment);
/// <summary>
/// Gets the XPAuthor exif tag.
/// </summary>
public static ExifTag<byte[]> XPAuthor => new ExifTag<byte[]>(ExifTagValue.XPAuthor);
/// <summary>
/// Gets the XPKeywords exif tag.
/// </summary>
public static ExifTag<byte[]> XPKeywords => new ExifTag<byte[]>(ExifTagValue.XPKeywords);
/// <summary>
/// Gets the XPSubject exif tag.
/// </summary>
public static ExifTag<byte[]> XPSubject => new ExifTag<byte[]>(ExifTagValue.XPSubject);
/// <summary> /// <summary>
/// Gets the GPSVersionID exif tag. /// Gets the GPSVersionID exif tag.
/// </summary> /// </summary>

24
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
{
/// <content/>
public abstract partial class ExifTag
{
/// <summary>
/// Gets the UserComment exif tag.
/// </summary>
public static ExifTag<EncodedString> UserComment { get; } = new ExifTag<EncodedString>(ExifTagValue.UserComment);
/// <summary>
/// Gets the GPSProcessingMethod exif tag.
/// </summary>
public static ExifTag<EncodedString> GPSProcessingMethod { get; } = new ExifTag<EncodedString>(ExifTagValue.GPSProcessingMethod);
/// <summary>
/// Gets the GPSAreaInformation exif tag.
/// </summary>
public static ExifTag<EncodedString> GPSAreaInformation { get; } = new ExifTag<EncodedString>(ExifTagValue.GPSAreaInformation);
}
}

34
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
{
/// <content/>
public abstract partial class ExifTag
{
/// <summary>
/// Gets the title tag used by Windows (encoded in UCS2).
/// </summary>
public static ExifTag<string> XPTitle => new ExifTag<string>(ExifTagValue.XPTitle);
/// <summary>
/// Gets the comment tag used by Windows (encoded in UCS2).
/// </summary>
public static ExifTag<string> XPComment => new ExifTag<string>(ExifTagValue.XPComment);
/// <summary>
/// Gets the author tag used by Windows (encoded in UCS2).
/// </summary>
public static ExifTag<string> XPAuthor => new ExifTag<string>(ExifTagValue.XPAuthor);
/// <summary>
/// Gets the keywords tag used by Windows (encoded in UCS2).
/// </summary>
public static ExifTag<string> XPKeywords => new ExifTag<string>(ExifTagValue.XPKeywords);
/// <summary>
/// Gets the subject tag used by Windows (encoded in UCS2).
/// </summary>
public static ExifTag<string> XPSubject => new ExifTag<string>(ExifTagValue.XPSubject);
}
}

15
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs

@ -31,11 +31,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary> /// </summary>
public static ExifTag<byte[]> MakerNote { get; } = new ExifTag<byte[]>(ExifTagValue.MakerNote); public static ExifTag<byte[]> MakerNote { get; } = new ExifTag<byte[]>(ExifTagValue.MakerNote);
/// <summary>
/// Gets the UserComment exif tag.
/// </summary>
public static ExifTag<byte[]> UserComment { get; } = new ExifTag<byte[]>(ExifTagValue.UserComment);
/// <summary> /// <summary>
/// Gets the FlashpixVersion exif tag. /// Gets the FlashpixVersion exif tag.
/// </summary> /// </summary>
@ -71,16 +66,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary> /// </summary>
public static ExifTag<byte[]> ImageSourceData { get; } = new ExifTag<byte[]>(ExifTagValue.ImageSourceData); public static ExifTag<byte[]> ImageSourceData { get; } = new ExifTag<byte[]>(ExifTagValue.ImageSourceData);
/// <summary>
/// Gets the GPSProcessingMethod exif tag.
/// </summary>
public static ExifTag<byte[]> GPSProcessingMethod { get; } = new ExifTag<byte[]>(ExifTagValue.GPSProcessingMethod);
/// <summary>
/// Gets the GPSAreaInformation exif tag.
/// </summary>
public static ExifTag<byte[]> GPSAreaInformation { get; } = new ExifTag<byte[]>(ExifTagValue.GPSAreaInformation);
/// <summary> /// <summary>
/// Gets the FileSource exif tag. /// Gets the FileSource exif tag.
/// </summary> /// </summary>

94
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
{
/// <summary>
/// The EXIF encoded string structure.
/// </summary>
public readonly struct EncodedString : IEquatable<EncodedString>
{
/// <summary>
/// Initializes a new instance of the <see cref="EncodedString" /> struct.
/// Default use Unicode character code.
/// </summary>
/// <param name="text">The text value.</param>
public EncodedString(string text)
: this(CharacterCode.Unicode, text)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EncodedString" /> struct.
/// </summary>
/// <param name="code">The character code.</param>
/// <param name="text">The text value.</param>
public EncodedString(CharacterCode code, string text)
{
this.Text = text;
this.Code = code;
}
/// <summary>
/// The 8-byte character code enum.
/// </summary>
public enum CharacterCode
{
/// <summary>
/// The ASCII (ITU-T T.50 IA5) character code.
/// </summary>
ASCII,
/// <summary>
/// The JIS (X208-1990) character code.
/// </summary>
JIS,
/// <summary>
/// The Unicode character code.
/// </summary>
Unicode,
/// <summary>
/// The undefined character code.
/// </summary>
Undefined
}
/// <summary>
/// Gets the character ode.
/// </summary>
public CharacterCode Code { get; }
/// <summary>
/// Gets the text.
/// </summary>
public string Text { get; }
/// <summary>
/// Converts the specified <see cref="string"/> to an instance of this type.
/// </summary>
/// <param name="text">The text value.</param>
public static implicit operator EncodedString(string text) => new(text);
/// <summary>
/// Converts the specified <see cref="EncodedString"/> to a <see cref="string"/>.
/// </summary>
/// <param name="encodedString">The <see cref="EncodedString"/> to convert.</param>
public static explicit operator string(EncodedString encodedString) => encodedString.Text;
/// <inheritdoc/>
public override bool Equals(object obj) => obj is EncodedString other && this.Equals(other);
/// <inheritdoc/>
public bool Equals(EncodedString other) => this.Text == other.Text && this.Code == other.Code;
/// <inheritdoc/>
public override int GetHashCode() => HashCode.Combine(this.Text, this.Code);
/// <inheritdoc/>
public override string ToString() => this.Text;
}
}

55
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<EncodedString>
{
public ExifEncodedString(ExifTag<EncodedString> 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);
}
}

24
src/ImageSharp/Processing/OrientationMode.cs → 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. // Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{ {
/// <summary> /// <summary>
/// Enumerates the available orientation values supplied by EXIF metadata. /// Enumerates the available orientation values supplied by EXIF metadata.
/// </summary> /// </summary>
internal enum OrientationMode : ushort public static class ExifOrientationMode
{ {
/// <summary> /// <summary>
/// Unknown rotation. /// Unknown rotation.
/// </summary> /// </summary>
Unknown = 0, public const ushort Unknown = 0;
/// <summary> /// <summary>
/// The 0th row at the top, the 0th column on the left. /// The 0th row at the top, the 0th column on the left.
/// </summary> /// </summary>
TopLeft = 1, public const ushort TopLeft = 1;
/// <summary> /// <summary>
/// The 0th row at the top, the 0th column on the right. /// The 0th row at the top, the 0th column on the right.
/// </summary> /// </summary>
TopRight = 2, public const ushort TopRight = 2;
/// <summary> /// <summary>
/// The 0th row at the bottom, the 0th column on the right. /// The 0th row at the bottom, the 0th column on the right.
/// </summary> /// </summary>
BottomRight = 3, public const ushort BottomRight = 3;
/// <summary> /// <summary>
/// The 0th row at the bottom, the 0th column on the left. /// The 0th row at the bottom, the 0th column on the left.
/// </summary> /// </summary>
BottomLeft = 4, public const ushort BottomLeft = 4;
/// <summary> /// <summary>
/// The 0th row on the left, the 0th column at the top. /// The 0th row on the left, the 0th column at the top.
/// </summary> /// </summary>
LeftTop = 5, public const ushort LeftTop = 5;
/// <summary> /// <summary>
/// The 0th row at the right, the 0th column at the top. /// The 0th row at the right, the 0th column at the top.
/// </summary> /// </summary>
RightTop = 6, public const ushort RightTop = 6;
/// <summary> /// <summary>
/// The 0th row on the right, the 0th column at the bottom. /// The 0th row on the right, the 0th column at the bottom.
/// </summary> /// </summary>
RightBottom = 7, public const ushort RightBottom = 7;
/// <summary> /// <summary>
/// The 0th row on the left, the 0th column at the bottom. /// The 0th row on the left, the 0th column at the bottom.
/// </summary> /// </summary>
LeftBottom = 8 public const ushort LeftBottom = 8;
} }
} }

47
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<string>
{
public ExifUcs2String(ExifTag<string> 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);
}
}

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

@ -15,21 +15,36 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{ {
switch (dataType) switch (dataType)
{ {
case ExifDataType.Byte: return isArray ? (ExifValue)new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType); case ExifDataType.Byte:
case ExifDataType.DoubleFloat: return isArray ? (ExifValue)new ExifDoubleArray(tag) : new ExifDouble(tag); return isArray ? new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType);
case ExifDataType.SingleFloat: return isArray ? (ExifValue)new ExifFloatArray(tag) : new ExifFloat(tag); case ExifDataType.DoubleFloat:
case ExifDataType.Long: return isArray ? (ExifValue)new ExifLongArray(tag) : new ExifLong(tag); return isArray ? new ExifDoubleArray(tag) : new ExifDouble(tag);
case ExifDataType.Long8: return isArray ? (ExifValue)new ExifLong8Array(tag) : new ExifLong8(tag); case ExifDataType.SingleFloat:
case ExifDataType.Rational: return isArray ? (ExifValue)new ExifRationalArray(tag) : new ExifRational(tag); return isArray ? new ExifFloatArray(tag) : new ExifFloat(tag);
case ExifDataType.Short: return isArray ? (ExifValue)new ExifShortArray(tag) : new ExifShort(tag); case ExifDataType.Long:
case ExifDataType.SignedByte: return isArray ? (ExifValue)new ExifSignedByteArray(tag) : new ExifSignedByte(tag); return isArray ? new ExifLongArray(tag) : new ExifLong(tag);
case ExifDataType.SignedLong: return isArray ? (ExifValue)new ExifSignedLongArray(tag) : new ExifSignedLong(tag); case ExifDataType.Long8:
case ExifDataType.SignedLong8: return isArray ? (ExifValue)new ExifSignedLong8Array(tag) : new ExifSignedLong8(tag); return isArray ? new ExifLong8Array(tag) : new ExifLong8(tag);
case ExifDataType.SignedRational: return isArray ? (ExifValue)new ExifSignedRationalArray(tag) : new ExifSignedRational(tag); case ExifDataType.Rational:
case ExifDataType.SignedShort: return isArray ? (ExifValue)new ExifSignedShortArray(tag) : new ExifSignedShort(tag); return isArray ? new ExifRationalArray(tag) : new ExifRational(tag);
case ExifDataType.Ascii: return new ExifString(tag); case ExifDataType.Short:
case ExifDataType.Undefined: return isArray ? (ExifValue)new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType); return isArray ? new ExifShortArray(tag) : new ExifShort(tag);
default: return null; 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) switch (tag)
{ {
case ExifTagValue.FaxProfile: return new ExifByte(ExifTag.FaxProfile, ExifDataType.Byte); case ExifTagValue.FaxProfile:
case ExifTagValue.ModeNumber: return new ExifByte(ExifTag.ModeNumber, ExifDataType.Byte); return new ExifByte(ExifTag.FaxProfile, ExifDataType.Byte);
case ExifTagValue.GPSAltitudeRef: return new ExifByte(ExifTag.GPSAltitudeRef, 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.ClipPath:
case ExifTagValue.VersionYear: return new ExifByteArray(ExifTag.VersionYear, ExifDataType.Byte); return new ExifByteArray(ExifTag.ClipPath, ExifDataType.Byte);
case ExifTagValue.XMP: return new ExifByteArray(ExifTag.XMP, ExifDataType.Byte); case ExifTagValue.VersionYear:
case ExifTagValue.CFAPattern2: return new ExifByteArray(ExifTag.CFAPattern2, ExifDataType.Byte); return new ExifByteArray(ExifTag.VersionYear, ExifDataType.Byte);
case ExifTagValue.TIFFEPStandardID: return new ExifByteArray(ExifTag.TIFFEPStandardID, ExifDataType.Byte); case ExifTagValue.XMP:
case ExifTagValue.XPTitle: return new ExifByteArray(ExifTag.XPTitle, ExifDataType.Byte); return new ExifByteArray(ExifTag.XMP, ExifDataType.Byte);
case ExifTagValue.XPComment: return new ExifByteArray(ExifTag.XPComment, ExifDataType.Byte); case ExifTagValue.CFAPattern2:
case ExifTagValue.XPAuthor: return new ExifByteArray(ExifTag.XPAuthor, ExifDataType.Byte); return new ExifByteArray(ExifTag.CFAPattern2, ExifDataType.Byte);
case ExifTagValue.XPKeywords: return new ExifByteArray(ExifTag.XPKeywords, ExifDataType.Byte); case ExifTagValue.TIFFEPStandardID:
case ExifTagValue.XPSubject: return new ExifByteArray(ExifTag.XPSubject, ExifDataType.Byte); return new ExifByteArray(ExifTag.TIFFEPStandardID, ExifDataType.Byte);
case ExifTagValue.GPSVersionID: return new ExifByteArray(ExifTag.GPSVersionID, ExifDataType.Byte); case ExifTagValue.GPSVersionID:
return new ExifByteArray(ExifTag.GPSVersionID, ExifDataType.Byte);
case ExifTagValue.PixelScale: return new ExifDoubleArray(ExifTag.PixelScale); case ExifTagValue.PixelScale:
case ExifTagValue.IntergraphMatrix: return new ExifDoubleArray(ExifTag.IntergraphMatrix); return new ExifDoubleArray(ExifTag.PixelScale);
case ExifTagValue.ModelTiePoint: return new ExifDoubleArray(ExifTag.ModelTiePoint); case ExifTagValue.IntergraphMatrix:
case ExifTagValue.ModelTransform: return new ExifDoubleArray(ExifTag.ModelTransform); 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.SubfileType:
case ExifTagValue.SubIFDOffset: return new ExifLong(ExifTag.SubIFDOffset); return new ExifLong(ExifTag.SubfileType);
case ExifTagValue.GPSIFDOffset: return new ExifLong(ExifTag.GPSIFDOffset); case ExifTagValue.SubIFDOffset:
case ExifTagValue.T4Options: return new ExifLong(ExifTag.T4Options); return new ExifLong(ExifTag.SubIFDOffset);
case ExifTagValue.T6Options: return new ExifLong(ExifTag.T6Options); case ExifTagValue.GPSIFDOffset:
case ExifTagValue.XClipPathUnits: return new ExifLong(ExifTag.XClipPathUnits); return new ExifLong(ExifTag.GPSIFDOffset);
case ExifTagValue.YClipPathUnits: return new ExifLong(ExifTag.YClipPathUnits); case ExifTagValue.T4Options:
case ExifTagValue.ProfileType: return new ExifLong(ExifTag.ProfileType); return new ExifLong(ExifTag.T4Options);
case ExifTagValue.CodingMethods: return new ExifLong(ExifTag.CodingMethods); case ExifTagValue.T6Options:
case ExifTagValue.T82ptions: return new ExifLong(ExifTag.T82ptions); return new ExifLong(ExifTag.T6Options);
case ExifTagValue.JPEGInterchangeFormat: return new ExifLong(ExifTag.JPEGInterchangeFormat); case ExifTagValue.XClipPathUnits:
case ExifTagValue.JPEGInterchangeFormatLength: return new ExifLong(ExifTag.JPEGInterchangeFormatLength); return new ExifLong(ExifTag.XClipPathUnits);
case ExifTagValue.MDFileTag: return new ExifLong(ExifTag.MDFileTag); case ExifTagValue.YClipPathUnits:
case ExifTagValue.StandardOutputSensitivity: return new ExifLong(ExifTag.StandardOutputSensitivity); return new ExifLong(ExifTag.YClipPathUnits);
case ExifTagValue.RecommendedExposureIndex: return new ExifLong(ExifTag.RecommendedExposureIndex); case ExifTagValue.ProfileType:
case ExifTagValue.ISOSpeed: return new ExifLong(ExifTag.ISOSpeed); return new ExifLong(ExifTag.ProfileType);
case ExifTagValue.ISOSpeedLatitudeyyy: return new ExifLong(ExifTag.ISOSpeedLatitudeyyy); case ExifTagValue.CodingMethods:
case ExifTagValue.ISOSpeedLatitudezzz: return new ExifLong(ExifTag.ISOSpeedLatitudezzz); return new ExifLong(ExifTag.CodingMethods);
case ExifTagValue.FaxRecvParams: return new ExifLong(ExifTag.FaxRecvParams); case ExifTagValue.T82ptions:
case ExifTagValue.FaxRecvTime: return new ExifLong(ExifTag.FaxRecvTime); return new ExifLong(ExifTag.T82ptions);
case ExifTagValue.ImageNumber: return new ExifLong(ExifTag.ImageNumber); 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.FreeOffsets:
case ExifTagValue.FreeByteCounts: return new ExifLongArray(ExifTag.FreeByteCounts); return new ExifLongArray(ExifTag.FreeOffsets);
case ExifTagValue.ColorResponseUnit: return new ExifLongArray(ExifTag.ColorResponseUnit); case ExifTagValue.FreeByteCounts:
case ExifTagValue.TileOffsets: return new ExifLongArray(ExifTag.TileOffsets); return new ExifLongArray(ExifTag.FreeByteCounts);
case ExifTagValue.SMinSampleValue: return new ExifLongArray(ExifTag.SMinSampleValue); case ExifTagValue.ColorResponseUnit:
case ExifTagValue.SMaxSampleValue: return new ExifLongArray(ExifTag.SMaxSampleValue); return new ExifLongArray(ExifTag.ColorResponseUnit);
case ExifTagValue.JPEGQTables: return new ExifLongArray(ExifTag.JPEGQTables); case ExifTagValue.TileOffsets:
case ExifTagValue.JPEGDCTables: return new ExifLongArray(ExifTag.JPEGDCTables); return new ExifLongArray(ExifTag.TileOffsets);
case ExifTagValue.JPEGACTables: return new ExifLongArray(ExifTag.JPEGACTables); case ExifTagValue.SMinSampleValue:
case ExifTagValue.StripRowCounts: return new ExifLongArray(ExifTag.StripRowCounts); return new ExifLongArray(ExifTag.SMinSampleValue);
case ExifTagValue.IntergraphRegisters: return new ExifLongArray(ExifTag.IntergraphRegisters); case ExifTagValue.SMaxSampleValue:
case ExifTagValue.TimeZoneOffset: return new ExifLongArray(ExifTag.TimeZoneOffset); return new ExifLongArray(ExifTag.SMaxSampleValue);
case ExifTagValue.SubIFDs: return new ExifLongArray(ExifTag.SubIFDs); 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.ImageWidth:
case ExifTagValue.ImageLength: return new ExifNumber(ExifTag.ImageLength); return new ExifNumber(ExifTag.ImageWidth);
case ExifTagValue.RowsPerStrip: return new ExifNumber(ExifTag.RowsPerStrip); case ExifTagValue.ImageLength:
case ExifTagValue.TileWidth: return new ExifNumber(ExifTag.TileWidth); return new ExifNumber(ExifTag.ImageLength);
case ExifTagValue.TileLength: return new ExifNumber(ExifTag.TileLength); case ExifTagValue.RowsPerStrip:
case ExifTagValue.BadFaxLines: return new ExifNumber(ExifTag.BadFaxLines); return new ExifNumber(ExifTag.RowsPerStrip);
case ExifTagValue.ConsecutiveBadFaxLines: return new ExifNumber(ExifTag.ConsecutiveBadFaxLines); case ExifTagValue.TileWidth:
case ExifTagValue.PixelXDimension: return new ExifNumber(ExifTag.PixelXDimension); return new ExifNumber(ExifTag.TileWidth);
case ExifTagValue.PixelYDimension: return new ExifNumber(ExifTag.PixelYDimension); 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.StripByteCounts:
case ExifTagValue.StripOffsets: return new ExifNumberArray(ExifTag.StripOffsets); return new ExifNumberArray(ExifTag.StripByteCounts);
case ExifTagValue.TileByteCounts: return new ExifNumberArray(ExifTag.TileByteCounts); case ExifTagValue.StripOffsets:
case ExifTagValue.ImageLayer: return new ExifNumberArray(ExifTag.ImageLayer); 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.XPosition:
case ExifTagValue.YPosition: return new ExifRational(ExifTag.YPosition); return new ExifRational(ExifTag.XPosition);
case ExifTagValue.XResolution: return new ExifRational(ExifTag.XResolution); case ExifTagValue.YPosition:
case ExifTagValue.YResolution: return new ExifRational(ExifTag.YResolution); return new ExifRational(ExifTag.YPosition);
case ExifTagValue.BatteryLevel: return new ExifRational(ExifTag.BatteryLevel); case ExifTagValue.XResolution:
case ExifTagValue.ExposureTime: return new ExifRational(ExifTag.ExposureTime); return new ExifRational(ExifTag.XResolution);
case ExifTagValue.FNumber: return new ExifRational(ExifTag.FNumber); case ExifTagValue.YResolution:
case ExifTagValue.MDScalePixel: return new ExifRational(ExifTag.MDScalePixel); return new ExifRational(ExifTag.YResolution);
case ExifTagValue.CompressedBitsPerPixel: return new ExifRational(ExifTag.CompressedBitsPerPixel); case ExifTagValue.BatteryLevel:
case ExifTagValue.ApertureValue: return new ExifRational(ExifTag.ApertureValue); return new ExifRational(ExifTag.BatteryLevel);
case ExifTagValue.MaxApertureValue: return new ExifRational(ExifTag.MaxApertureValue); case ExifTagValue.ExposureTime:
case ExifTagValue.SubjectDistance: return new ExifRational(ExifTag.SubjectDistance); return new ExifRational(ExifTag.ExposureTime);
case ExifTagValue.FocalLength: return new ExifRational(ExifTag.FocalLength); case ExifTagValue.FNumber:
case ExifTagValue.FlashEnergy2: return new ExifRational(ExifTag.FlashEnergy2); return new ExifRational(ExifTag.FNumber);
case ExifTagValue.FocalPlaneXResolution2: return new ExifRational(ExifTag.FocalPlaneXResolution2); case ExifTagValue.MDScalePixel:
case ExifTagValue.FocalPlaneYResolution2: return new ExifRational(ExifTag.FocalPlaneYResolution2); return new ExifRational(ExifTag.MDScalePixel);
case ExifTagValue.ExposureIndex2: return new ExifRational(ExifTag.ExposureIndex2); case ExifTagValue.CompressedBitsPerPixel:
case ExifTagValue.Humidity: return new ExifRational(ExifTag.Humidity); return new ExifRational(ExifTag.CompressedBitsPerPixel);
case ExifTagValue.Pressure: return new ExifRational(ExifTag.Pressure); case ExifTagValue.ApertureValue:
case ExifTagValue.Acceleration: return new ExifRational(ExifTag.Acceleration); return new ExifRational(ExifTag.ApertureValue);
case ExifTagValue.FlashEnergy: return new ExifRational(ExifTag.FlashEnergy); case ExifTagValue.MaxApertureValue:
case ExifTagValue.FocalPlaneXResolution: return new ExifRational(ExifTag.FocalPlaneXResolution); return new ExifRational(ExifTag.MaxApertureValue);
case ExifTagValue.FocalPlaneYResolution: return new ExifRational(ExifTag.FocalPlaneYResolution); case ExifTagValue.SubjectDistance:
case ExifTagValue.ExposureIndex: return new ExifRational(ExifTag.ExposureIndex); return new ExifRational(ExifTag.SubjectDistance);
case ExifTagValue.DigitalZoomRatio: return new ExifRational(ExifTag.DigitalZoomRatio); case ExifTagValue.FocalLength:
case ExifTagValue.GPSAltitude: return new ExifRational(ExifTag.GPSAltitude); return new ExifRational(ExifTag.FocalLength);
case ExifTagValue.GPSDOP: return new ExifRational(ExifTag.GPSDOP); case ExifTagValue.FlashEnergy2:
case ExifTagValue.GPSSpeed: return new ExifRational(ExifTag.GPSSpeed); return new ExifRational(ExifTag.FlashEnergy2);
case ExifTagValue.GPSTrack: return new ExifRational(ExifTag.GPSTrack); case ExifTagValue.FocalPlaneXResolution2:
case ExifTagValue.GPSImgDirection: return new ExifRational(ExifTag.GPSImgDirection); return new ExifRational(ExifTag.FocalPlaneXResolution2);
case ExifTagValue.GPSDestBearing: return new ExifRational(ExifTag.GPSDestBearing); case ExifTagValue.FocalPlaneYResolution2:
case ExifTagValue.GPSDestDistance: return new ExifRational(ExifTag.GPSDestDistance); 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.WhitePoint:
case ExifTagValue.PrimaryChromaticities: return new ExifRationalArray(ExifTag.PrimaryChromaticities); return new ExifRationalArray(ExifTag.WhitePoint);
case ExifTagValue.YCbCrCoefficients: return new ExifRationalArray(ExifTag.YCbCrCoefficients); case ExifTagValue.PrimaryChromaticities:
case ExifTagValue.ReferenceBlackWhite: return new ExifRationalArray(ExifTag.ReferenceBlackWhite); return new ExifRationalArray(ExifTag.PrimaryChromaticities);
case ExifTagValue.GPSLatitude: return new ExifRationalArray(ExifTag.GPSLatitude); case ExifTagValue.YCbCrCoefficients:
case ExifTagValue.GPSLongitude: return new ExifRationalArray(ExifTag.GPSLongitude); return new ExifRationalArray(ExifTag.YCbCrCoefficients);
case ExifTagValue.GPSTimestamp: return new ExifRationalArray(ExifTag.GPSTimestamp); case ExifTagValue.ReferenceBlackWhite:
case ExifTagValue.GPSDestLatitude: return new ExifRationalArray(ExifTag.GPSDestLatitude); return new ExifRationalArray(ExifTag.ReferenceBlackWhite);
case ExifTagValue.GPSDestLongitude: return new ExifRationalArray(ExifTag.GPSDestLongitude); case ExifTagValue.GPSLatitude:
case ExifTagValue.LensSpecification: return new ExifRationalArray(ExifTag.LensSpecification); 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.OldSubfileType:
case ExifTagValue.Compression: return new ExifShort(ExifTag.Compression); return new ExifShort(ExifTag.OldSubfileType);
case ExifTagValue.PhotometricInterpretation: return new ExifShort(ExifTag.PhotometricInterpretation); case ExifTagValue.Compression:
case ExifTagValue.Thresholding: return new ExifShort(ExifTag.Thresholding); return new ExifShort(ExifTag.Compression);
case ExifTagValue.CellWidth: return new ExifShort(ExifTag.CellWidth); case ExifTagValue.PhotometricInterpretation:
case ExifTagValue.CellLength: return new ExifShort(ExifTag.CellLength); return new ExifShort(ExifTag.PhotometricInterpretation);
case ExifTagValue.FillOrder: return new ExifShort(ExifTag.FillOrder); case ExifTagValue.Thresholding:
case ExifTagValue.Orientation: return new ExifShort(ExifTag.Orientation); return new ExifShort(ExifTag.Thresholding);
case ExifTagValue.SamplesPerPixel: return new ExifShort(ExifTag.SamplesPerPixel); case ExifTagValue.CellWidth:
case ExifTagValue.PlanarConfiguration: return new ExifShort(ExifTag.PlanarConfiguration); return new ExifShort(ExifTag.CellWidth);
case ExifTagValue.Predictor: return new ExifShort(ExifTag.Predictor); case ExifTagValue.CellLength:
case ExifTagValue.GrayResponseUnit: return new ExifShort(ExifTag.GrayResponseUnit); return new ExifShort(ExifTag.CellLength);
case ExifTagValue.ResolutionUnit: return new ExifShort(ExifTag.ResolutionUnit); case ExifTagValue.FillOrder:
case ExifTagValue.CleanFaxData: return new ExifShort(ExifTag.CleanFaxData); return new ExifShort(ExifTag.FillOrder);
case ExifTagValue.InkSet: return new ExifShort(ExifTag.InkSet); case ExifTagValue.Orientation:
case ExifTagValue.NumberOfInks: return new ExifShort(ExifTag.NumberOfInks); return new ExifShort(ExifTag.Orientation);
case ExifTagValue.DotRange: return new ExifShort(ExifTag.DotRange); case ExifTagValue.SamplesPerPixel:
case ExifTagValue.Indexed: return new ExifShort(ExifTag.Indexed); return new ExifShort(ExifTag.SamplesPerPixel);
case ExifTagValue.OPIProxy: return new ExifShort(ExifTag.OPIProxy); case ExifTagValue.PlanarConfiguration:
case ExifTagValue.JPEGProc: return new ExifShort(ExifTag.JPEGProc); return new ExifShort(ExifTag.PlanarConfiguration);
case ExifTagValue.JPEGRestartInterval: return new ExifShort(ExifTag.JPEGRestartInterval); case ExifTagValue.Predictor:
case ExifTagValue.YCbCrPositioning: return new ExifShort(ExifTag.YCbCrPositioning); return new ExifShort(ExifTag.Predictor);
case ExifTagValue.Rating: return new ExifShort(ExifTag.Rating); case ExifTagValue.GrayResponseUnit:
case ExifTagValue.RatingPercent: return new ExifShort(ExifTag.RatingPercent); return new ExifShort(ExifTag.GrayResponseUnit);
case ExifTagValue.ExposureProgram: return new ExifShort(ExifTag.ExposureProgram); case ExifTagValue.ResolutionUnit:
case ExifTagValue.Interlace: return new ExifShort(ExifTag.Interlace); return new ExifShort(ExifTag.ResolutionUnit);
case ExifTagValue.SelfTimerMode: return new ExifShort(ExifTag.SelfTimerMode); case ExifTagValue.CleanFaxData:
case ExifTagValue.SensitivityType: return new ExifShort(ExifTag.SensitivityType); return new ExifShort(ExifTag.CleanFaxData);
case ExifTagValue.MeteringMode: return new ExifShort(ExifTag.MeteringMode); case ExifTagValue.InkSet:
case ExifTagValue.LightSource: return new ExifShort(ExifTag.LightSource); return new ExifShort(ExifTag.InkSet);
case ExifTagValue.FocalPlaneResolutionUnit2: return new ExifShort(ExifTag.FocalPlaneResolutionUnit2); case ExifTagValue.NumberOfInks:
case ExifTagValue.SensingMethod2: return new ExifShort(ExifTag.SensingMethod2); return new ExifShort(ExifTag.NumberOfInks);
case ExifTagValue.Flash: return new ExifShort(ExifTag.Flash); case ExifTagValue.DotRange:
case ExifTagValue.ColorSpace: return new ExifShort(ExifTag.ColorSpace); return new ExifShort(ExifTag.DotRange);
case ExifTagValue.FocalPlaneResolutionUnit: return new ExifShort(ExifTag.FocalPlaneResolutionUnit); case ExifTagValue.Indexed:
case ExifTagValue.SensingMethod: return new ExifShort(ExifTag.SensingMethod); return new ExifShort(ExifTag.Indexed);
case ExifTagValue.CustomRendered: return new ExifShort(ExifTag.CustomRendered); case ExifTagValue.OPIProxy:
case ExifTagValue.ExposureMode: return new ExifShort(ExifTag.ExposureMode); return new ExifShort(ExifTag.OPIProxy);
case ExifTagValue.WhiteBalance: return new ExifShort(ExifTag.WhiteBalance); case ExifTagValue.JPEGProc:
case ExifTagValue.FocalLengthIn35mmFilm: return new ExifShort(ExifTag.FocalLengthIn35mmFilm); return new ExifShort(ExifTag.JPEGProc);
case ExifTagValue.SceneCaptureType: return new ExifShort(ExifTag.SceneCaptureType); case ExifTagValue.JPEGRestartInterval:
case ExifTagValue.GainControl: return new ExifShort(ExifTag.GainControl); return new ExifShort(ExifTag.JPEGRestartInterval);
case ExifTagValue.Contrast: return new ExifShort(ExifTag.Contrast); case ExifTagValue.YCbCrPositioning:
case ExifTagValue.Saturation: return new ExifShort(ExifTag.Saturation); return new ExifShort(ExifTag.YCbCrPositioning);
case ExifTagValue.Sharpness: return new ExifShort(ExifTag.Sharpness); case ExifTagValue.Rating:
case ExifTagValue.SubjectDistanceRange: return new ExifShort(ExifTag.SubjectDistanceRange); return new ExifShort(ExifTag.Rating);
case ExifTagValue.GPSDifferential: return new ExifShort(ExifTag.GPSDifferential); 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.BitsPerSample:
case ExifTagValue.MinSampleValue: return new ExifShortArray(ExifTag.MinSampleValue); return new ExifShortArray(ExifTag.BitsPerSample);
case ExifTagValue.MaxSampleValue: return new ExifShortArray(ExifTag.MaxSampleValue); case ExifTagValue.MinSampleValue:
case ExifTagValue.GrayResponseCurve: return new ExifShortArray(ExifTag.GrayResponseCurve); return new ExifShortArray(ExifTag.MinSampleValue);
case ExifTagValue.ColorMap: return new ExifShortArray(ExifTag.ColorMap); case ExifTagValue.MaxSampleValue:
case ExifTagValue.ExtraSamples: return new ExifShortArray(ExifTag.ExtraSamples); return new ExifShortArray(ExifTag.MaxSampleValue);
case ExifTagValue.PageNumber: return new ExifShortArray(ExifTag.PageNumber); case ExifTagValue.GrayResponseCurve:
case ExifTagValue.TransferFunction: return new ExifShortArray(ExifTag.TransferFunction); return new ExifShortArray(ExifTag.GrayResponseCurve);
case ExifTagValue.HalftoneHints: return new ExifShortArray(ExifTag.HalftoneHints); case ExifTagValue.ColorMap:
case ExifTagValue.SampleFormat: return new ExifShortArray(ExifTag.SampleFormat); return new ExifShortArray(ExifTag.ColorMap);
case ExifTagValue.TransferRange: return new ExifShortArray(ExifTag.TransferRange); case ExifTagValue.ExtraSamples:
case ExifTagValue.DefaultImageColor: return new ExifShortArray(ExifTag.DefaultImageColor); return new ExifShortArray(ExifTag.ExtraSamples);
case ExifTagValue.JPEGLosslessPredictors: return new ExifShortArray(ExifTag.JPEGLosslessPredictors); case ExifTagValue.PageNumber:
case ExifTagValue.JPEGPointTransforms: return new ExifShortArray(ExifTag.JPEGPointTransforms); return new ExifShortArray(ExifTag.PageNumber);
case ExifTagValue.YCbCrSubsampling: return new ExifShortArray(ExifTag.YCbCrSubsampling); case ExifTagValue.TransferFunction:
case ExifTagValue.CFARepeatPatternDim: return new ExifShortArray(ExifTag.CFARepeatPatternDim); return new ExifShortArray(ExifTag.TransferFunction);
case ExifTagValue.IntergraphPacketData: return new ExifShortArray(ExifTag.IntergraphPacketData); case ExifTagValue.HalftoneHints:
case ExifTagValue.ISOSpeedRatings: return new ExifShortArray(ExifTag.ISOSpeedRatings); return new ExifShortArray(ExifTag.HalftoneHints);
case ExifTagValue.SubjectArea: return new ExifShortArray(ExifTag.SubjectArea); case ExifTagValue.SampleFormat:
case ExifTagValue.SubjectLocation: return new ExifShortArray(ExifTag.SubjectLocation); 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.ShutterSpeedValue:
case ExifTagValue.BrightnessValue: return new ExifSignedRational(ExifTag.BrightnessValue); return new ExifSignedRational(ExifTag.ShutterSpeedValue);
case ExifTagValue.ExposureBiasValue: return new ExifSignedRational(ExifTag.ExposureBiasValue); case ExifTagValue.BrightnessValue:
case ExifTagValue.AmbientTemperature: return new ExifSignedRational(ExifTag.AmbientTemperature); return new ExifSignedRational(ExifTag.BrightnessValue);
case ExifTagValue.WaterDepth: return new ExifSignedRational(ExifTag.WaterDepth); case ExifTagValue.ExposureBiasValue:
case ExifTagValue.CameraElevationAngle: return new ExifSignedRational(ExifTag.CameraElevationAngle); 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.ImageDescription:
case ExifTagValue.Make: return new ExifString(ExifTag.Make); return new ExifString(ExifTag.ImageDescription);
case ExifTagValue.Model: return new ExifString(ExifTag.Model); case ExifTagValue.Make:
case ExifTagValue.Software: return new ExifString(ExifTag.Software); return new ExifString(ExifTag.Make);
case ExifTagValue.DateTime: return new ExifString(ExifTag.DateTime); case ExifTagValue.Model:
case ExifTagValue.Artist: return new ExifString(ExifTag.Artist); return new ExifString(ExifTag.Model);
case ExifTagValue.HostComputer: return new ExifString(ExifTag.HostComputer); case ExifTagValue.Software:
case ExifTagValue.Copyright: return new ExifString(ExifTag.Copyright); return new ExifString(ExifTag.Software);
case ExifTagValue.DocumentName: return new ExifString(ExifTag.DocumentName); case ExifTagValue.DateTime:
case ExifTagValue.PageName: return new ExifString(ExifTag.PageName); return new ExifString(ExifTag.DateTime);
case ExifTagValue.InkNames: return new ExifString(ExifTag.InkNames); case ExifTagValue.Artist:
case ExifTagValue.TargetPrinter: return new ExifString(ExifTag.TargetPrinter); return new ExifString(ExifTag.Artist);
case ExifTagValue.ImageID: return new ExifString(ExifTag.ImageID); case ExifTagValue.HostComputer:
case ExifTagValue.MDLabName: return new ExifString(ExifTag.MDLabName); return new ExifString(ExifTag.HostComputer);
case ExifTagValue.MDSampleInfo: return new ExifString(ExifTag.MDSampleInfo); case ExifTagValue.Copyright:
case ExifTagValue.MDPrepDate: return new ExifString(ExifTag.MDPrepDate); return new ExifString(ExifTag.Copyright);
case ExifTagValue.MDPrepTime: return new ExifString(ExifTag.MDPrepTime); case ExifTagValue.DocumentName:
case ExifTagValue.MDFileUnits: return new ExifString(ExifTag.MDFileUnits); return new ExifString(ExifTag.DocumentName);
case ExifTagValue.SEMInfo: return new ExifString(ExifTag.SEMInfo); case ExifTagValue.PageName:
case ExifTagValue.SpectralSensitivity: return new ExifString(ExifTag.SpectralSensitivity); return new ExifString(ExifTag.PageName);
case ExifTagValue.DateTimeOriginal: return new ExifString(ExifTag.DateTimeOriginal); case ExifTagValue.InkNames:
case ExifTagValue.DateTimeDigitized: return new ExifString(ExifTag.DateTimeDigitized); return new ExifString(ExifTag.InkNames);
case ExifTagValue.SubsecTime: return new ExifString(ExifTag.SubsecTime); case ExifTagValue.TargetPrinter:
case ExifTagValue.SubsecTimeOriginal: return new ExifString(ExifTag.SubsecTimeOriginal); return new ExifString(ExifTag.TargetPrinter);
case ExifTagValue.SubsecTimeDigitized: return new ExifString(ExifTag.SubsecTimeDigitized); case ExifTagValue.ImageID:
case ExifTagValue.RelatedSoundFile: return new ExifString(ExifTag.RelatedSoundFile); return new ExifString(ExifTag.ImageID);
case ExifTagValue.FaxSubaddress: return new ExifString(ExifTag.FaxSubaddress); case ExifTagValue.MDLabName:
case ExifTagValue.OffsetTime: return new ExifString(ExifTag.OffsetTime); return new ExifString(ExifTag.MDLabName);
case ExifTagValue.OffsetTimeOriginal: return new ExifString(ExifTag.OffsetTimeOriginal); case ExifTagValue.MDSampleInfo:
case ExifTagValue.OffsetTimeDigitized: return new ExifString(ExifTag.OffsetTimeDigitized); return new ExifString(ExifTag.MDSampleInfo);
case ExifTagValue.SecurityClassification: return new ExifString(ExifTag.SecurityClassification); case ExifTagValue.MDPrepDate:
case ExifTagValue.ImageHistory: return new ExifString(ExifTag.ImageHistory); return new ExifString(ExifTag.MDPrepDate);
case ExifTagValue.ImageUniqueID: return new ExifString(ExifTag.ImageUniqueID); case ExifTagValue.MDPrepTime:
case ExifTagValue.OwnerName: return new ExifString(ExifTag.OwnerName); return new ExifString(ExifTag.MDPrepTime);
case ExifTagValue.SerialNumber: return new ExifString(ExifTag.SerialNumber); case ExifTagValue.MDFileUnits:
case ExifTagValue.LensMake: return new ExifString(ExifTag.LensMake); return new ExifString(ExifTag.MDFileUnits);
case ExifTagValue.LensModel: return new ExifString(ExifTag.LensModel); case ExifTagValue.SEMInfo:
case ExifTagValue.LensSerialNumber: return new ExifString(ExifTag.LensSerialNumber); return new ExifString(ExifTag.SEMInfo);
case ExifTagValue.GDALMetadata: return new ExifString(ExifTag.GDALMetadata); case ExifTagValue.SpectralSensitivity:
case ExifTagValue.GDALNoData: return new ExifString(ExifTag.GDALNoData); return new ExifString(ExifTag.SpectralSensitivity);
case ExifTagValue.GPSLatitudeRef: return new ExifString(ExifTag.GPSLatitudeRef); case ExifTagValue.DateTimeOriginal:
case ExifTagValue.GPSLongitudeRef: return new ExifString(ExifTag.GPSLongitudeRef); return new ExifString(ExifTag.DateTimeOriginal);
case ExifTagValue.GPSSatellites: return new ExifString(ExifTag.GPSSatellites); case ExifTagValue.DateTimeDigitized:
case ExifTagValue.GPSStatus: return new ExifString(ExifTag.GPSStatus); return new ExifString(ExifTag.DateTimeDigitized);
case ExifTagValue.GPSMeasureMode: return new ExifString(ExifTag.GPSMeasureMode); case ExifTagValue.SubsecTime:
case ExifTagValue.GPSSpeedRef: return new ExifString(ExifTag.GPSSpeedRef); return new ExifString(ExifTag.SubsecTime);
case ExifTagValue.GPSTrackRef: return new ExifString(ExifTag.GPSTrackRef); case ExifTagValue.SubsecTimeOriginal:
case ExifTagValue.GPSImgDirectionRef: return new ExifString(ExifTag.GPSImgDirectionRef); return new ExifString(ExifTag.SubsecTimeOriginal);
case ExifTagValue.GPSMapDatum: return new ExifString(ExifTag.GPSMapDatum); case ExifTagValue.SubsecTimeDigitized:
case ExifTagValue.GPSDestLatitudeRef: return new ExifString(ExifTag.GPSDestLatitudeRef); return new ExifString(ExifTag.SubsecTimeDigitized);
case ExifTagValue.GPSDestLongitudeRef: return new ExifString(ExifTag.GPSDestLongitudeRef); case ExifTagValue.RelatedSoundFile:
case ExifTagValue.GPSDestBearingRef: return new ExifString(ExifTag.GPSDestBearingRef); return new ExifString(ExifTag.RelatedSoundFile);
case ExifTagValue.GPSDestDistanceRef: return new ExifString(ExifTag.GPSDestDistanceRef); case ExifTagValue.FaxSubaddress:
case ExifTagValue.GPSDateStamp: return new ExifString(ExifTag.GPSDateStamp); 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.FileSource:
case ExifTagValue.SceneType: return new ExifByte(ExifTag.SceneType, ExifDataType.Undefined); 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.JPEGTables:
case ExifTagValue.OECF: return new ExifByteArray(ExifTag.OECF, ExifDataType.Undefined); return new ExifByteArray(ExifTag.JPEGTables, ExifDataType.Undefined);
case ExifTagValue.ExifVersion: return new ExifByteArray(ExifTag.ExifVersion, ExifDataType.Undefined); case ExifTagValue.OECF:
case ExifTagValue.ComponentsConfiguration: return new ExifByteArray(ExifTag.ComponentsConfiguration, ExifDataType.Undefined); return new ExifByteArray(ExifTag.OECF, ExifDataType.Undefined);
case ExifTagValue.MakerNote: return new ExifByteArray(ExifTag.MakerNote, ExifDataType.Undefined); case ExifTagValue.ExifVersion:
case ExifTagValue.UserComment: return new ExifByteArray(ExifTag.UserComment, ExifDataType.Undefined); return new ExifByteArray(ExifTag.ExifVersion, ExifDataType.Undefined);
case ExifTagValue.FlashpixVersion: return new ExifByteArray(ExifTag.FlashpixVersion, ExifDataType.Undefined); case ExifTagValue.ComponentsConfiguration:
case ExifTagValue.SpatialFrequencyResponse: return new ExifByteArray(ExifTag.SpatialFrequencyResponse, ExifDataType.Undefined); return new ExifByteArray(ExifTag.ComponentsConfiguration, ExifDataType.Undefined);
case ExifTagValue.SpatialFrequencyResponse2: return new ExifByteArray(ExifTag.SpatialFrequencyResponse2, ExifDataType.Undefined); case ExifTagValue.MakerNote:
case ExifTagValue.Noise: return new ExifByteArray(ExifTag.Noise, ExifDataType.Undefined); return new ExifByteArray(ExifTag.MakerNote, ExifDataType.Undefined);
case ExifTagValue.CFAPattern: return new ExifByteArray(ExifTag.CFAPattern, ExifDataType.Undefined); case ExifTagValue.FlashpixVersion:
case ExifTagValue.DeviceSettingDescription: return new ExifByteArray(ExifTag.DeviceSettingDescription, ExifDataType.Undefined); return new ExifByteArray(ExifTag.FlashpixVersion, ExifDataType.Undefined);
case ExifTagValue.ImageSourceData: return new ExifByteArray(ExifTag.ImageSourceData, ExifDataType.Undefined); case ExifTagValue.SpatialFrequencyResponse:
case ExifTagValue.GPSProcessingMethod: return new ExifByteArray(ExifTag.GPSProcessingMethod, ExifDataType.Undefined); return new ExifByteArray(ExifTag.SpatialFrequencyResponse, ExifDataType.Undefined);
case ExifTagValue.GPSAreaInformation: return new ExifByteArray(ExifTag.GPSAreaInformation, 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;
} }
} }
} }

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

@ -28,42 +28,42 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeImageApply() protected override void BeforeImageApply()
{ {
OrientationMode orientation = GetExifOrientation(this.Source); ushort orientation = GetExifOrientation(this.Source);
Size size = this.SourceRectangle.Size; Size size = this.SourceRectangle.Size;
switch (orientation) switch (orientation)
{ {
case OrientationMode.TopRight: case ExifOrientationMode.TopRight:
new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle);
break; break;
case OrientationMode.BottomRight: case ExifOrientationMode.BottomRight:
new RotateProcessor((int)RotateMode.Rotate180, size).Execute(this.Configuration, this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate180, size).Execute(this.Configuration, this.Source, this.SourceRectangle);
break; break;
case OrientationMode.BottomLeft: case ExifOrientationMode.BottomLeft:
new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle);
break; break;
case OrientationMode.LeftTop: case ExifOrientationMode.LeftTop:
new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle);
new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle);
break; break;
case OrientationMode.RightTop: case ExifOrientationMode.RightTop:
new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle);
break; break;
case OrientationMode.RightBottom: case ExifOrientationMode.RightBottom:
new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle);
new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle);
break; break;
case OrientationMode.LeftBottom: case ExifOrientationMode.LeftBottom:
new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle);
break; break;
case OrientationMode.Unknown: case ExifOrientationMode.Unknown:
case OrientationMode.TopLeft: case ExifOrientationMode.TopLeft:
default: default:
break; break;
} }
@ -81,32 +81,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Returns the current EXIF orientation /// Returns the current EXIF orientation
/// </summary> /// </summary>
/// <param name="source">The image to auto rotate.</param> /// <param name="source">The image to auto rotate.</param>
/// <returns>The <see cref="OrientationMode"/></returns> /// <returns>The <see cref="ushort"/></returns>
private static OrientationMode GetExifOrientation(Image<TPixel> source) private static ushort GetExifOrientation(Image<TPixel> source)
{ {
if (source.Metadata.ExifProfile is null) if (source.Metadata.ExifProfile is null)
{ {
return OrientationMode.Unknown; return ExifOrientationMode.Unknown;
} }
IExifValue<ushort> value = source.Metadata.ExifProfile.GetValue(ExifTag.Orientation); IExifValue<ushort> value = source.Metadata.ExifProfile.GetValue(ExifTag.Orientation);
if (value is null) if (value is null)
{ {
return OrientationMode.Unknown; return ExifOrientationMode.Unknown;
} }
OrientationMode orientation; ushort orientation;
if (value.DataType == ExifDataType.Short) if (value.DataType == ExifDataType.Short)
{ {
orientation = (OrientationMode)value.Value; orientation = value.Value;
} }
else else
{ {
orientation = (OrientationMode)Convert.ToUInt16(value.Value); orientation = Convert.ToUInt16(value.Value);
source.Metadata.ExifProfile.RemoveValue(ExifTag.Orientation); 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; return orientation;
} }

3
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 memoryStream = new MemoryStream(this.jpegBytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, memoryStream); 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); var scanDecoder = new HuffmanScanDecoder(bufferedStream, new NoopSpectralConverter(), cancellationToken: default);
decoder.ParseStream(bufferedStream, scanDecoder, 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 // We want to test only stream parsing and scan decoding, we don't need to convert spectral data to actual pixels

12
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -247,5 +247,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
"Disco") "Disco")
.Dispose(); .Dispose();
} }
// https://github.com/SixLabors/ImageSharp/issues/1962
[Theory]
[WithFile(TestImages.Gif.Issues.Issue1962NoColorTable, PixelTypes.Rgba32)]
public void Issue1962<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider);
image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider);
}
} }
} }

3
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs

@ -42,6 +42,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// High depth images // High depth images
TestImages.Jpeg.Baseline.Testorig12bit, 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 = public static string[] ProgressiveTestJpegs =

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

@ -4,6 +4,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
@ -300,5 +301,72 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}); });
Assert.Null(ex); 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);
}
} }
} }

4
tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// Calculating data from ImageSharp // Calculating data from ImageSharp
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; 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 ms = new MemoryStream(sourceBytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms);
@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// Calculating data from ImageSharp // Calculating data from ImageSharp
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; 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 ms = new MemoryStream(sourceBytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms);

2
tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs

@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// Decoding // Decoding
using var converter = new SpectralConverter<TPixel>(Configuration.Default); using var converter = new SpectralConverter<TPixel>(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); var scanDecoder = new HuffmanScanDecoder(bufferedStream, converter, cancellationToken: default);
decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default); decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default);

122
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<IDisposable> 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<byte>(length, 1024) :
Allocator.Allocate<byte>(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<byte>(length, 1024);
}
else
{
_ = Allocator.Allocate<byte>(length);
}
}
}
}
}

2
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; } public int ReleaseInvocationCount { get; private set; }

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

@ -51,6 +51,9 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif
{ ExifTag.ImageDescription, "ImageDescription" }, { ExifTag.ImageDescription, "ImageDescription" },
{ ExifTag.ExposureTime, new Rational(1.0 / 1600.0) }, { ExifTag.ExposureTime, new Rational(1.0 / 1600.0) },
{ ExifTag.Model, "Model" }, { 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] [Theory]
@ -504,7 +507,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif
public void IfdStructure() public void IfdStructure()
{ {
var exif = new ExifProfile(); var exif = new ExifProfile();
exif.SetValue(ExifTag.XPAuthor, Encoding.GetEncoding("UCS-2").GetBytes("Dan Petitt")); exif.SetValue(ExifTag.XPAuthor, "Dan Petitt");
Span<byte> actualBytes = exif.ToByteArray(); Span<byte> actualBytes = exif.ToByteArray();

65
tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Text;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using Xunit; using Xunit;
@ -23,11 +25,6 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values
{ ExifTag.XMP }, { ExifTag.XMP },
{ ExifTag.CFAPattern2 }, { ExifTag.CFAPattern2 },
{ ExifTag.TIFFEPStandardID }, { ExifTag.TIFFEPStandardID },
{ ExifTag.XPTitle },
{ ExifTag.XPComment },
{ ExifTag.XPAuthor },
{ ExifTag.XPKeywords },
{ ExifTag.XPSubject },
{ ExifTag.GPSVersionID }, { ExifTag.GPSVersionID },
}; };
@ -295,7 +292,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values
{ ExifTag.GPSDestLongitudeRef }, { ExifTag.GPSDestLongitudeRef },
{ ExifTag.GPSDestBearingRef }, { ExifTag.GPSDestBearingRef },
{ ExifTag.GPSDestDistanceRef }, { ExifTag.GPSDestDistanceRef },
{ ExifTag.GPSDateStamp } { ExifTag.GPSDateStamp },
}; };
public static TheoryData<ExifTag> UndefinedTags => new TheoryData<ExifTag> public static TheoryData<ExifTag> UndefinedTags => new TheoryData<ExifTag>
@ -311,7 +308,6 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values
{ ExifTag.ExifVersion }, { ExifTag.ExifVersion },
{ ExifTag.ComponentsConfiguration }, { ExifTag.ComponentsConfiguration },
{ ExifTag.MakerNote }, { ExifTag.MakerNote },
{ ExifTag.UserComment },
{ ExifTag.FlashpixVersion }, { ExifTag.FlashpixVersion },
{ ExifTag.SpatialFrequencyResponse }, { ExifTag.SpatialFrequencyResponse },
{ ExifTag.SpatialFrequencyResponse2 }, { ExifTag.SpatialFrequencyResponse2 },
@ -319,10 +315,24 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values
{ ExifTag.CFAPattern }, { ExifTag.CFAPattern },
{ ExifTag.DeviceSettingDescription }, { ExifTag.DeviceSettingDescription },
{ ExifTag.ImageSourceData }, { ExifTag.ImageSourceData },
};
public static TheoryData<ExifTag> EncodedStringTags => new TheoryData<ExifTag>
{
{ ExifTag.UserComment },
{ ExifTag.GPSProcessingMethod }, { ExifTag.GPSProcessingMethod },
{ ExifTag.GPSAreaInformation } { ExifTag.GPSAreaInformation }
}; };
public static TheoryData<ExifTag> Ucs2StringTags => new TheoryData<ExifTag>
{
{ ExifTag.XPTitle },
{ ExifTag.XPComment },
{ ExifTag.XPAuthor },
{ ExifTag.XPKeywords },
{ ExifTag.XPSubject },
};
[Theory] [Theory]
[MemberData(nameof(ByteTags))] [MemberData(nameof(ByteTags))]
public void ExifByteTests(ExifTag tag) public void ExifByteTests(ExifTag tag)
@ -593,5 +603,46 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values
var typed = (ExifByteArray)value; var typed = (ExifByteArray)value;
Assert.Equal(expected, typed.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);
}
} }
} }

71
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 const string FlipTestFile = TestImages.Bmp.F;
public static readonly TheoryData<ExifDataType, byte[]> InvalidOrientationValues public static readonly TheoryData<ExifDataType, byte[]> InvalidOrientationValues
= new TheoryData<ExifDataType, byte[]> = new()
{ {
{ ExifDataType.Byte, new byte[] { 1 } }, { ExifDataType.Byte, new byte[] { 1 } },
{ ExifDataType.SignedByte, new byte[] { 2 } }, { ExifDataType.SignedByte, new byte[] { 2 } },
{ ExifDataType.SignedShort, BitConverter.GetBytes((short)3) }, { ExifDataType.SignedShort, BitConverter.GetBytes((short)3) },
{ ExifDataType.Long, BitConverter.GetBytes(4U) }, { ExifDataType.Long, BitConverter.GetBytes(4U) },
{ ExifDataType.SignedLong, BitConverter.GetBytes(5) } { ExifDataType.SignedLong, BitConverter.GetBytes(5) }
}; };
public static readonly TheoryData<ushort> ExifOrientationValues = new TheoryData<ushort> public static readonly TheoryData<ushort> ExifOrientationValues
{ = new()
0, {
1, ExifOrientationMode.Unknown,
2, ExifOrientationMode.TopLeft,
3, ExifOrientationMode.TopRight,
4, ExifOrientationMode.BottomRight,
5, ExifOrientationMode.BottomLeft,
6, ExifOrientationMode.LeftTop,
7, ExifOrientationMode.RightTop,
8 ExifOrientationMode.RightBottom,
}; ExifOrientationMode.LeftBottom
};
[Theory] [Theory]
[WithFile(FlipTestFile, nameof(ExifOrientationValues), PixelTypes.Rgba32)] [WithFile(FlipTestFile, nameof(ExifOrientationValues), PixelTypes.Rgba32)]
public void AutoOrient_WorksForAllExifOrientations<TPixel>(TestImageProvider<TPixel> provider, ushort orientation) public void AutoOrient_WorksForAllExifOrientations<TPixel>(TestImageProvider<TPixel> provider, ushort orientation)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage()) using Image<TPixel> image = provider.GetImage();
{ image.Metadata.ExifProfile = new ExifProfile();
image.Metadata.ExifProfile = new ExifProfile(); image.Metadata.ExifProfile.SetValue(ExifTag.Orientation, orientation);
image.Metadata.ExifProfile.SetValue(ExifTag.Orientation, orientation);
image.Mutate(x => x.AutoOrient()); image.Mutate(x => x.AutoOrient());
image.DebugSave(provider, orientation, appendPixelTypeToFileName: false); image.DebugSave(provider, orientation, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(provider, orientation, appendPixelTypeToFileName: false); image.CompareToReferenceOutput(provider, orientation, appendPixelTypeToFileName: false);
}
} }
[Theory] [Theory]
@ -76,19 +75,17 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
// Change the number of components // Change the number of components
bytes[20] = 1; bytes[20] = 1;
var orientationCodeData = new byte[8]; byte[] orientationCodeData = new byte[8];
Array.Copy(orientation, orientationCodeData, orientation.Length); Array.Copy(orientation, orientationCodeData, orientation.Length);
ulong orientationCode = BitConverter.ToUInt64(orientationCodeData, 0); ulong orientationCode = BitConverter.ToUInt64(orientationCodeData, 0);
using (Image<TPixel> image = provider.GetImage()) using Image<TPixel> image = provider.GetImage();
using (Image<TPixel> reference = image.Clone()) using Image<TPixel> reference = image.Clone();
{ image.Metadata.ExifProfile = new ExifProfile(bytes);
image.Metadata.ExifProfile = new ExifProfile(bytes); image.Mutate(x => x.AutoOrient());
image.Mutate(x => x.AutoOrient()); image.DebugSave(provider, $"{dataType}-{orientationCode}", appendPixelTypeToFileName: false);
image.DebugSave(provider, $"{dataType}-{orientationCode}", appendPixelTypeToFileName: false); ImageComparer.Exact.VerifySimilarity(image, reference);
ImageComparer.Exact.VerifySimilarity(image, reference);
}
} }
} }
} }

3
tests/ImageSharp.Tests/TestImages.cs

@ -192,6 +192,7 @@ namespace SixLabors.ImageSharp.Tests
public const string Exif = "Jpg/baseline/exif.jpg"; public const string Exif = "Jpg/baseline/exif.jpg";
public const string Floorplan = "Jpg/baseline/Floorplan.jpg"; public const string Floorplan = "Jpg/baseline/Floorplan.jpg";
public const string Calliphora = "Jpg/baseline/Calliphora.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 Ycck = "Jpg/baseline/ycck.jpg";
public const string Turtle420 = "Jpg/baseline/turtle.jpg"; public const string Turtle420 = "Jpg/baseline/turtle.jpg";
public const string GammaDalaiLamaGray = "Jpg/baseline/gamma_dalai_lama_gray.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 Winter444_Interleaved = "Jpg/baseline/winter444_interleaved.jpg";
public const string Metadata = "Jpg/baseline/Metadata-test-file.jpg"; public const string Metadata = "Jpg/baseline/Metadata-test-file.jpg";
public const string ExtendedXmp = "Jpg/baseline/extended-xmp.jpg"; public const string ExtendedXmp = "Jpg/baseline/extended-xmp.jpg";
public const string GrayscaleSampling2x2 = "Jpg/baseline/grayscale_sampling22.jpg";
public static readonly string[] All = 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 Issue1505 = "Gif/issues/issue1505_argumentoutofrange.png";
public const string Issue1530 = "Gif/issues/issue1530.gif"; public const string Issue1530 = "Gif/issues/issue1530.gif";
public const string InvalidColorIndex = "Gif/issues/issue1668_invalidcolorindex.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 }; public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 };

3
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

3
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

3
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

3
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

3
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
Loading…
Cancel
Save