Browse Source

Merge branch 'master' into master

af/merge-core
James Jackson-South 8 years ago
committed by GitHub
parent
commit
e4bac03b0d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
  2. 88
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  3. 56
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  4. 9
      src/ImageSharp/Formats/Png/PngChunkType.cs
  5. 14
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  6. 19
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  7. 15
      src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs
  8. 5
      src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs
  9. 21
      src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs
  10. 30
      src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs
  11. 11
      src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs
  12. 2
      tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
  13. 1
      tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs
  14. 2
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  15. 1
      tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs
  16. 207
      tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs
  17. 3
      tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs

2
src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public static readonly byte[] AdobeMarker = Encoding.UTF8.GetBytes("Adobe");
/// <summary>
/// Returns a value indicating whether the passed bytes are a match to the profile identifer
/// Returns a value indicating whether the passed bytes are a match to the profile identifier
/// </summary>
/// <param name="bytesToCheck">The bytes to check</param>
/// <param name="profileIdentifier">The profile identifier</param>

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

@ -5,6 +5,7 @@ using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Helpers;
@ -69,10 +70,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private ushort resetInterval;
/// <summary>
/// Whether the image has a EXIF header
/// Whether the image has an EXIF marker
/// </summary>
private bool isExif;
/// <summary>
/// Contains exif data
/// </summary>
private byte[] exifData;
/// <summary>
/// Whether the image has an ICC marker
/// </summary>
private bool isIcc;
/// <summary>
/// Contains ICC data
/// </summary>
private byte[] iccData;
/// <summary>
/// Contains information about the JFIF marker
/// </summary>
@ -201,6 +217,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
where TPixel : struct, IPixel<TPixel>
{
this.ParseStream(stream);
this.InitExifProfile();
this.InitIccProfile();
this.InitDerivedMetaDataProperties();
return this.PostProcessIntoImage<TPixel>();
}
@ -212,6 +230,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public IImageInfo Identify(Stream stream)
{
this.ParseStream(stream, true);
this.InitExifProfile();
this.InitIccProfile();
this.InitDerivedMetaDataProperties();
return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.MetaData);
}
@ -403,6 +423,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.ComponentCount}");
}
/// <summary>
/// Initializes the EXIF profile.
/// </summary>
private void InitExifProfile()
{
if (this.isExif)
{
this.MetaData.ExifProfile = new ExifProfile(this.exifData);
}
}
/// <summary>
/// Initializes the ICC profile.
/// </summary>
private void InitIccProfile()
{
if (this.isIcc)
{
var profile = new IccProfile(this.iccData);
if (profile.CheckIsValid())
{
this.MetaData.IccProfile = profile;
}
}
}
/// <summary>
/// Assigns derived metadata properties to <see cref="MetaData"/>, eg. horizontal and vertical resolution if it has a JFIF header.
/// </summary>
@ -431,11 +477,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.MetaData.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(this.MetaData.ExifProfile);
}
}
}
if (this.MetaData.IccProfile?.CheckIsValid() == false)
{
this.MetaData.IccProfile = null;
}
/// <summary>
/// Extends the profile with additional data.
/// </summary>
/// <param name="profile">The profile data array.</param>
/// <param name="extension">The array containing addition profile data.</param>
private void ExtendProfile(ref byte[] profile, byte[] extension)
{
int currentLength = profile.Length;
Array.Resize(ref profile, currentLength + extension.Length);
Buffer.BlockCopy(extension, 0, profile, currentLength, extension.Length);
}
/// <summary>
@ -469,7 +523,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApp1Marker(int remaining)
{
if (remaining < 6 || this.IgnoreMetadata)
const int Exif00 = 6;
if (remaining < Exif00 || this.IgnoreMetadata)
{
// Skip the application header length
this.InputStream.Skip(remaining);
@ -482,7 +537,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker))
{
this.isExif = true;
this.MetaData.ExifProfile = new ExifProfile(profile);
if (this.exifData == null)
{
// The first 6 bytes (Exif00) will be skipped, because this is Jpeg specific
this.exifData = profile.Skip(Exif00).ToArray();
}
else
{
// If the EXIF information exceeds 64K, it will be split over multiple APP1 markers
this.ExtendProfile(ref this.exifData, profile.Skip(Exif00).ToArray());
}
}
}
@ -506,16 +570,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker))
{
this.isIcc = true;
byte[] profile = new byte[remaining];
this.InputStream.Read(profile, 0, remaining);
if (this.MetaData.IccProfile == null)
if (this.iccData == null)
{
this.MetaData.IccProfile = new IccProfile(profile);
this.iccData = profile;
}
else
{
this.MetaData.IccProfile.Extend(profile);
// If the ICC information exceeds 64K, it will be split over multiple APP2 markers
this.ExtendProfile(ref this.iccData, profile);
}
}
else
@ -630,7 +696,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <param name="frameMarker">The current frame marker.</param>
/// <param name="metadataOnly">Whether to parse metadata only</param>
private void ProcessStartOfFrameMarker(int remaining, JpegFileMarker frameMarker, bool metadataOnly)
private void ProcessStartOfFrameMarker(int remaining, in JpegFileMarker frameMarker, bool metadataOnly)
{
if (this.Frame != null)
{

56
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -4,9 +4,11 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
@ -214,6 +216,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Write the Start Of Image marker.
this.WriteApplicationHeader(image.MetaData);
// Write Exif and ICC profiles
this.WriteProfiles(image);
// Write the quantization tables.
@ -430,7 +433,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="meta">The image meta data.</param>
private void WriteApplicationHeader(ImageMetaData meta)
{
// Write the start of image marker. Markers are always prefixed with with 0xff.
// Write the start of image marker. Markers are always prefixed with 0xff.
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.SOI;
@ -620,27 +623,59 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </exception>
private void WriteExifProfile(ExifProfile exifProfile)
{
const int Max = 65533;
const int MaxBytesApp1 = 65533;
const int MaxBytesWithExifId = 65527;
byte[] data = exifProfile?.ToByteArray();
if (data == null || data.Length == 0)
{
return;
}
if (data.Length > Max)
data = ProfileResolver.ExifMarker.Concat(data).ToArray();
int remaining = data.Length;
int bytesToWrite = remaining > MaxBytesApp1 ? MaxBytesApp1 : remaining;
int app1Length = bytesToWrite + 2;
this.WriteApp1Header(app1Length);
// write the exif data
this.outputStream.Write(data, 0, bytesToWrite);
remaining -= bytesToWrite;
// if the exif data exceeds 64K, write it in multiple APP1 Markers
for (int idx = MaxBytesApp1; idx < data.Length; idx += MaxBytesWithExifId)
{
throw new ImageFormatException($"Exif profile size exceeds limit. nameof{Max}");
}
bytesToWrite = remaining > MaxBytesWithExifId ? MaxBytesWithExifId : remaining;
app1Length = bytesToWrite + 2 + 6;
int length = data.Length + 2;
this.WriteApp1Header(app1Length);
// write Exif00 marker
ProfileResolver.ExifMarker.AsSpan().CopyTo(this.buffer.AsSpan());
this.outputStream.Write(this.buffer, 0, 6);
// write the exif data
this.outputStream.Write(data, idx, bytesToWrite);
remaining -= bytesToWrite;
}
}
/// <summary>
/// Writes the App1 header.
/// </summary>
/// <param name="app1Length">The length of the data the app1 marker contains</param>
private void WriteApp1Header(int app1Length)
{
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.APP1; // Application Marker
this.buffer[2] = (byte)((length >> 8) & 0xFF);
this.buffer[3] = (byte)(length & 0xFF);
this.buffer[2] = (byte)((app1Length >> 8) & 0xFF);
this.buffer[3] = (byte)(app1Length & 0xFF);
this.outputStream.Write(this.buffer, 0, 4);
this.outputStream.Write(data, 0, data.Length);
}
/// <summary>
@ -652,7 +687,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </exception>
private void WriteIccProfile(IccProfile iccProfile)
{
// Just incase someone set the value to null by accident.
if (iccProfile == null)
{
return;
@ -908,7 +942,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="length">The marker length.</param>
private void WriteMarkerHeader(byte marker, int length)
{
// Markers are always prefixed with with 0xff.
// Markers are always prefixed with 0xff.
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = marker;
this.buffer[2] = (byte)(length >> 8);

9
src/ImageSharp/Formats/Png/PngChunkType.cs

@ -4,7 +4,7 @@
namespace SixLabors.ImageSharp.Formats.Png
{
/// <summary>
/// Contains a list of of chunk types.
/// Contains a list of chunk types.
/// </summary>
internal enum PngChunkType : uint
{
@ -55,6 +55,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image.
/// </summary>
Physical = 0x70485973U // pHYs
Physical = 0x70485973U, // pHYs
/// <summary>
/// The data chunk which contains the Exif profile.
/// </summary>
Exif = 0x65584966U // eXIf
}
}

14
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -14,6 +14,7 @@ using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
@ -259,6 +260,15 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngChunkType.Text:
this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length);
break;
case PngChunkType.Exif:
if (!this.ignoreMetadata)
{
byte[] exifData = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
metadata.ExifProfile = new ExifProfile(exifData);
}
break;
case PngChunkType.End:
this.isEndChunkReached = true;
@ -1170,7 +1180,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Decodes and assigns marker colors that identify transparent pixels in non indexed images
/// </summary>
/// <param name="alpha">The aplha tRNS array</param>
/// <param name="alpha">The alpha tRNS array</param>
private void AssignTransparentMarkers(ReadOnlySpan<byte> alpha)
{
if (this.pngColorType == PngColorType.Rgb)
@ -1217,7 +1227,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
/// <typeparam name="TPixel">The type of pixel we are expanding to</typeparam>
/// <param name="scanline">The defiltered scanline</param>
/// <param name="row">Thecurrent output image row</param>
/// <param name="row">The current output image row</param>
private void ProcessScanlineFromPalette<TPixel>(ReadOnlySpan<byte> scanline, Span<TPixel> row)
where TPixel : struct, IPixel<TPixel>
{

19
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -231,6 +231,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.WritePhysicalChunk(stream, image);
this.WriteGammaChunk(stream);
this.WriteExifChunk(stream, image);
this.WriteDataChunks(image.Frames.RootFrame, quantizedPixelsSpan, stream);
this.WriteEndChunk(stream);
stream.Flush();
@ -490,7 +491,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Calculates the correct number of bytes per pixel for the given color type.
/// </summary>
/// <returns>The <see cref="int"/></returns>
/// <returns>Bytes per pixel</returns>
private int CalculateBytesPerPixel()
{
switch (this.pngColorType)
@ -648,6 +649,22 @@ namespace SixLabors.ImageSharp.Formats.Png
this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9);
}
/// <summary>
/// Writes the eXIf chunk to the stream, if any EXIF Profile values are present in the meta data.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="image">The image.</param>
private void WriteExifChunk<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
if (image.MetaData.ExifProfile?.Values.Count > 0)
{
image.MetaData.SyncProfiles();
this.WriteChunk(stream, PngChunkType.Exif, image.MetaData.ExifProfile.ToByteArray());
}
}
/// <summary>
/// Writes the gamma information to the stream.
/// </summary>

15
src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs

@ -5,17 +5,18 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{
internal static class ExifConstants
{
public static readonly byte[] Header = {
(byte)'E',
(byte)'x',
(byte)'i',
(byte)'f',
0x00,
0x00,
public static readonly byte[] LittleEndianByteOrderMarker = {
(byte)'I',
(byte)'I',
0x2A,
0x00,
};
public static readonly byte[] BigEndianByteOrderMarker = {
(byte)'M',
(byte)'M',
0x00,
0x2A
};
}
}

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

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
@ -18,7 +17,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <summary>
/// The byte array to read the EXIF profile from.
/// </summary>
private readonly byte[] data;
private byte[] data;
/// <summary>
/// The collection of EXIF values
@ -86,7 +85,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
if (other.data != null)
{
this.data = new byte[other.data.Length];
Buffer.BlockCopy(other.data, 0, this.data, 0, other.data.Length);
other.data.AsSpan().CopyTo(this.data);
}
}

21
src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs

@ -25,7 +25,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
private Endianness endianness = Endianness.BigEndian;
private uint exifOffset;
private uint gpsOffset;
private int startIndex;
public ExifReader(byte[] exifData)
{
@ -77,20 +76,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{
var values = new List<ExifValue>();
if (this.ReadString(4) == "Exif")
{
if (this.ReadUInt16() != 0)
{
return values;
}
this.startIndex = 6;
}
else
{
this.position = 0;
}
if (this.ReadString(2) == "II")
{
this.endianness = Endianness.LittleEndian;
@ -169,7 +154,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <param name="index">The index.</param>
private void AddValues(List<ExifValue> values, int index)
{
this.position = this.startIndex + index;
this.position = index;
int count = this.ReadUInt16();
for (int i = 0; i < count; i++)
@ -353,7 +338,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{
int oldIndex = this.position;
uint newIndex = this.ConvertToUInt32(offsetBuffer) + (uint)this.startIndex;
uint newIndex = this.ConvertToUInt32(offsetBuffer);
// Ensure that the new index does not overrun the data
if (newIndex > int.MaxValue)
@ -454,7 +439,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{
if (value.Tag == ExifTag.JPEGInterchangeFormat && (value.DataType == ExifDataType.Long))
{
this.ThumbnailOffset = (uint)value.Value + (uint)this.startIndex;
this.ThumbnailOffset = (uint)value.Value;
}
else if (value.Tag == ExifTag.JPEGInterchangeFormatLength && value.DataType == ExifDataType.Long)
{

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

@ -14,11 +14,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// </summary>
internal sealed class ExifWriter
{
/// <summary>
/// The start index.
/// </summary>
private const int StartIndex = 6;
/// <summary>
/// Which parts will be written.
/// </summary>
@ -51,6 +46,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// </returns>
public byte[] GetData()
{
uint startIndex = 0;
uint length;
int exifIndex = -1;
int gpsIndex = -1;
@ -86,14 +82,20 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return null;
}
length += 10 + 4 + 2;
// two bytes for the byte Order marker 'II', followed by the number 42 (0x2A) and a 0, making 4 bytes total
length += (uint)ExifConstants.LittleEndianByteOrderMarker.Length;
length += 4 + 2;
byte[] result = new byte[length];
ExifConstants.Header.AsSpan().CopyTo(result); // 0-9
int i = 0;
// the byte order marker for little-endian, followed by the number 42 and a 0
ExifConstants.LittleEndianByteOrderMarker.AsSpan().CopyTo(result.AsSpan(start: i));
i += ExifConstants.LittleEndianByteOrderMarker.Length;
int i = 10;
uint ifdOffset = ((uint)i - StartIndex) + 4;
uint ifdOffset = ((uint)i - startIndex) + 4;
uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength;
if (exifLength > 0)
@ -109,18 +111,18 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
i = WriteUInt32(ifdOffset, result, i);
i = this.WriteHeaders(this.ifdIndexes, result, i);
i = WriteUInt32(thumbnailOffset, result, i);
i = this.WriteData(this.ifdIndexes, result, i);
i = this.WriteData(startIndex, this.ifdIndexes, result, i);
if (exifLength > 0)
{
i = this.WriteHeaders(this.exifIndexes, result, i);
i = this.WriteData(this.exifIndexes, result, i);
i = this.WriteData(startIndex, this.exifIndexes, result, i);
}
if (gpsLength > 0)
{
i = this.WriteHeaders(this.gpsIndexes, result, i);
i = this.WriteData(this.gpsIndexes, result, i);
i = this.WriteData(startIndex, this.gpsIndexes, result, i);
}
WriteUInt16((ushort)0, result, i);
@ -257,7 +259,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return newOffset;
}
private int WriteData(List<int> indexes, Span<byte> destination, int offset)
private int WriteData(uint startIndex, List<int> indexes, Span<byte> destination, int offset)
{
if (this.dataOffsets.Count == 0)
{
@ -272,7 +274,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
ExifValue value = this.values[index];
if (value.Length > 4)
{
WriteUInt32((uint)(newOffset - StartIndex), destination, this.dataOffsets[i++]);
WriteUInt32((uint)(newOffset - startIndex), destination, this.dataOffsets[i++]);
newOffset = this.WriteValue(value, destination, newOffset);
}
}

11
src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs

@ -149,17 +149,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
#endif
/// <summary>
/// Extends the profile with additional data.
/// </summary>
/// <param name="bytes">The array containing addition profile data.</param>
public void Extend(byte[] bytes)
{
int currentLength = this.data.Length;
Array.Resize(ref this.data, currentLength + bytes.Length);
Buffer.BlockCopy(bytes, 0, this.data, currentLength, bytes.Length);
}
/// <summary>
/// Checks for signs of a corrupt profile.
/// </summary>

2
tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj

@ -21,7 +21,7 @@
<PackageReference Include="BitMiracle.LibJpeg.NET" Version="1.4.280" />
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="7.5.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="Moq" Version="4.8.2" />
<PackageReference Include="Moq" Version="4.8.3" />
<!--<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />-->
<!--<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />-->
</ItemGroup>

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

@ -19,6 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
Assert.Equal(PngChunkType.Text, GetType("tEXt"));
Assert.Equal(PngChunkType.Gamma, GetType("gAMA"));
Assert.Equal(PngChunkType.Physical, GetType("pHYs"));
Assert.Equal(PngChunkType.Exif, GetType("eXIf"));
}
private static PngChunkType GetType(string text)

2
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -36,7 +36,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.2" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="Moq" Version="4.8.2" />
<PackageReference Include="Moq" Version="4.8.3" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json" />

1
tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;

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

@ -3,9 +3,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
@ -17,8 +19,27 @@ namespace SixLabors.ImageSharp.Tests
{
public class ExifProfileTests
{
[Fact]
public void Constructor()
public enum TestImageWriteFormat
{
Jpeg,
Png
}
private static readonly Dictionary<ExifTag, object> TestProfileValues = new Dictionary<ExifTag, object>()
{
{ ExifTag.Software, "Software" },
{ ExifTag.Copyright, "Copyright" },
{ ExifTag.Orientation, (ushort)5 },
{ ExifTag.ShutterSpeedValue, new SignedRational(75.55) },
{ ExifTag.ImageDescription, "ImageDescription" },
{ ExifTag.ExposureTime, new Rational(1.0 / 1600.0) },
{ ExifTag.Model, "Model" },
};
[Theory]
[InlineData(TestImageWriteFormat.Jpeg)]
[InlineData(TestImageWriteFormat.Png)]
public void Constructor(TestImageWriteFormat imageFormat)
{
Image<Rgba32> image = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora).CreateImage();
@ -27,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests
image.MetaData.ExifProfile = new ExifProfile();
image.MetaData.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra");
image = WriteAndRead(image);
image = WriteAndRead(image, imageFormat);
Assert.NotNull(image.MetaData.ExifProfile);
Assert.Equal(1, image.MetaData.ExifProfile.Values.Count());
@ -50,7 +71,7 @@ namespace SixLabors.ImageSharp.Tests
ExifProfile profile = GetExifProfile();
ExifProfile clone = new ExifProfile(profile);
var clone = new ExifProfile(profile);
TestProfile(clone);
profile.SetValue(ExifTag.ColorSpace, (ushort)2);
@ -59,10 +80,12 @@ namespace SixLabors.ImageSharp.Tests
TestProfile(clone);
}
[Fact]
public void WriteFraction()
[Theory]
[InlineData(TestImageWriteFormat.Jpeg)]
[InlineData(TestImageWriteFormat.Png)]
public void WriteFraction(TestImageWriteFormat imageFormat)
{
using (MemoryStream memStream = new MemoryStream())
using (var memStream = new MemoryStream())
{
double exposureTime = 1.0 / 1600;
@ -70,13 +93,10 @@ namespace SixLabors.ImageSharp.Tests
profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime));
Image<Rgba32> image = new Image<Rgba32>(1, 1);
var image = new Image<Rgba32>(1, 1);
image.MetaData.ExifProfile = profile;
image.SaveAsJpeg(memStream);
memStream.Position = 0;
image = Image.Load<Rgba32>(memStream);
image = WriteAndRead(image, imageFormat);
profile = image.MetaData.ExifProfile;
Assert.NotNull(profile);
@ -91,10 +111,7 @@ namespace SixLabors.ImageSharp.Tests
profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true));
image.MetaData.ExifProfile = profile;
image.SaveAsJpeg(memStream);
memStream.Position = 0;
image = Image.Load<Rgba32>(memStream);
image = WriteAndRead(image, imageFormat);
profile = image.MetaData.ExifProfile;
Assert.NotNull(profile);
@ -104,36 +121,40 @@ namespace SixLabors.ImageSharp.Tests
}
}
[Fact]
public void ReadWriteInfinity()
[Theory]
[InlineData(TestImageWriteFormat.Jpeg)]
[InlineData(TestImageWriteFormat.Png)]
public void ReadWriteInfinity(TestImageWriteFormat imageFormat)
{
Image<Rgba32> image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage();
image.MetaData.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity));
image = WriteAndRead(image);
image = WriteAndReadJpeg(image);
ExifValue value = image.MetaData.ExifProfile.GetValue(ExifTag.ExposureBiasValue);
Assert.NotNull(value);
Assert.Equal(new SignedRational(double.PositiveInfinity), value.Value);
image.MetaData.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity));
image = WriteAndRead(image);
image = WriteAndRead(image, imageFormat);
value = image.MetaData.ExifProfile.GetValue(ExifTag.ExposureBiasValue);
Assert.NotNull(value);
Assert.Equal(new SignedRational(double.NegativeInfinity), value.Value);
image.MetaData.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity));
image = WriteAndRead(image);
image = WriteAndRead(image, imageFormat);
value = image.MetaData.ExifProfile.GetValue(ExifTag.FlashEnergy);
Assert.NotNull(value);
Assert.Equal(new Rational(double.PositiveInfinity), value.Value);
}
[Fact]
public void SetValue()
[Theory]
[InlineData(TestImageWriteFormat.Jpeg)]
[InlineData(TestImageWriteFormat.Png)]
public void SetValue(TestImageWriteFormat imageFormat)
{
Rational[] latitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) };
var latitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) };
Image<Rgba32> image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage();
image.MetaData.ExifProfile.SetValue(ExifTag.Software, "ImageSharp");
@ -171,7 +192,7 @@ namespace SixLabors.ImageSharp.Tests
value = image.MetaData.ExifProfile.GetValue(ExifTag.GPSLatitude);
TestValue(value, latitude);
image = WriteAndRead(image);
image = WriteAndRead(image, imageFormat);
Assert.NotNull(image.MetaData.ExifProfile);
Assert.Equal(17, image.MetaData.ExifProfile.Values.Count());
@ -193,7 +214,7 @@ namespace SixLabors.ImageSharp.Tests
image.MetaData.ExifProfile.Parts = ExifParts.ExifTags;
image = WriteAndRead(image);
image = WriteAndRead(image, imageFormat);
Assert.NotNull(image.MetaData.ExifProfile);
Assert.Equal(8, image.MetaData.ExifProfile.Values.Count());
@ -252,22 +273,36 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal(170, thumbnail.Height);
}
[Fact]
public void WriteTooLargeProfile()
[Theory]
[InlineData(ExifTag.Software)]
[InlineData(ExifTag.Copyright)]
[InlineData(ExifTag.Model)]
[InlineData(ExifTag.ImageDescription)]
public void ReadWriteLargeProfileJpg(ExifTag exifValueToChange)
{
// arrange
var junk = new StringBuilder();
for (int i = 0; i < 65500; i++)
for (int i = 0; i < 65600; i++)
{
junk.Append("I");
junk.Append("a");
}
var image = new Image<Rgba32>(100, 100);
image.MetaData.ExifProfile = new ExifProfile();
image.MetaData.ExifProfile.SetValue(ExifTag.ImageDescription, junk.ToString());
using (var memStream = new MemoryStream())
ExifProfile expectedProfile = CreateExifProfile();
var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList();
expectedProfile.SetValue(exifValueToChange, junk.ToString());
image.MetaData.ExifProfile = expectedProfile;
// act
Image<Rgba32> reloadedImage = WriteAndRead(image, TestImageWriteFormat.Jpeg);
// assert
ExifProfile actualProfile = reloadedImage.MetaData.ExifProfile;
Assert.NotNull(actualProfile);
foreach (ExifTag expectedProfileTag in expectedProfileTags)
{
Assert.Throws<ImageFormatException>(() => image.SaveAsJpeg(memStream));
ExifValue actualProfileValue = actualProfile.GetValue(expectedProfileTag);
ExifValue expectedProfileValue = expectedProfile.GetValue(expectedProfileTag);
Assert.Equal(expectedProfileValue.Value, actualProfileValue.Value);
}
}
@ -305,10 +340,71 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal(24, profile.Values.Count);
byte[] bytes = profile.ToByteArray();
Assert.Equal(495, bytes.Length);
Assert.Equal(489, bytes.Length);
}
[Theory]
[InlineData(TestImageWriteFormat.Jpeg)]
[InlineData(TestImageWriteFormat.Png)]
public void WritingImagePreservesExifProfile(TestImageWriteFormat imageFormat)
{
// arrange
var image = new Image<Rgba32>(1, 1);
ExifProfile expected = CreateExifProfile();
image.MetaData.ExifProfile = expected;
// act
Image<Rgba32> reloadedImage = WriteAndRead(image, imageFormat);
// assert
ExifProfile actual = reloadedImage.MetaData.ExifProfile;
Assert.NotNull(actual);
foreach(KeyValuePair<ExifTag, object> expectedProfileValue in TestProfileValues)
{
ExifValue actualProfileValue = actual.GetValue(expectedProfileValue.Key);
Assert.NotNull(actualProfileValue);
Assert.Equal(expectedProfileValue.Value, actualProfileValue.Value);
}
}
[Fact]
public void ProfileToByteArray()
{
// arrange
byte[] exifBytesWithExifCode = ProfileResolver.ExifMarker.Concat(ExifConstants.LittleEndianByteOrderMarker).ToArray();
byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker;
ExifProfile expectedProfile = CreateExifProfile();
var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList();
// act
byte[] actualBytes = expectedProfile.ToByteArray();
var actualProfile = new ExifProfile(actualBytes);
// assert
Assert.NotNull(actualBytes);
Assert.NotEmpty(actualBytes);
Assert.Equal(exifBytesWithoutExifCode, actualBytes.Take(exifBytesWithoutExifCode.Length).ToArray());
foreach(ExifTag expectedProfileTag in expectedProfileTags)
{
ExifValue actualProfileValue = actualProfile.GetValue(expectedProfileTag);
ExifValue expectedProfileValue = expectedProfile.GetValue(expectedProfileTag);
Assert.Equal(expectedProfileValue.Value, actualProfileValue.Value);
}
}
private static ExifProfile GetExifProfile()
private static ExifProfile CreateExifProfile()
{
var profile = new ExifProfile();
foreach(KeyValuePair<ExifTag, object> exifProfileValue in TestProfileValues)
{
profile.SetValue(exifProfileValue.Key, exifProfileValue.Value);
}
return profile;
}
internal static ExifProfile GetExifProfile()
{
Image<Rgba32> image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage();
@ -318,9 +414,22 @@ namespace SixLabors.ImageSharp.Tests
return profile;
}
private static Image<Rgba32> WriteAndRead(Image<Rgba32> image)
private static Image<Rgba32> WriteAndRead(Image<Rgba32> image, TestImageWriteFormat imageFormat)
{
using (MemoryStream memStream = new MemoryStream())
switch(imageFormat)
{
case TestImageWriteFormat.Jpeg:
return WriteAndReadJpeg(image);
case TestImageWriteFormat.Png:
return WriteAndReadPng(image);
default:
throw new ArgumentException("unexpected test image format, only Jpeg and Png are allowed");
}
}
private static Image<Rgba32> WriteAndReadJpeg(Image<Rgba32> image)
{
using (var memStream = new MemoryStream())
{
image.SaveAsJpeg(memStream);
image.Dispose();
@ -330,6 +439,18 @@ namespace SixLabors.ImageSharp.Tests
}
}
private static Image<Rgba32> WriteAndReadPng(Image<Rgba32> image)
{
using (var memStream = new MemoryStream())
{
image.SaveAsPng(memStream);
image.Dispose();
memStream.Position = 0;
return Image.Load<Rgba32>(memStream);
}
}
private static void TestProfile(ExifProfile profile)
{
Assert.NotNull(profile);
@ -341,13 +462,19 @@ namespace SixLabors.ImageSharp.Tests
Assert.NotNull(value.Value);
if (value.Tag == ExifTag.Software)
{
Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.ToString());
}
if (value.Tag == ExifTag.XResolution)
{
Assert.Equal(new Rational(300.0), value.Value);
}
if (value.Tag == ExifTag.PixelXDimension)
{
Assert.Equal(2338U, value.Value);
}
}
}

3
tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs

@ -5,12 +5,11 @@ using System;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
using SixLabors.ImageSharp.Processing;
public class AutoOrientTests : FileTestBase
{
public static readonly string[] FlipFiles = { TestImages.Bmp.F };

Loading…
Cancel
Save