Browse Source

Merge branch 'master' into release/rc-1

pull/1574/head
James Jackson-South 6 years ago
parent
commit
0b4b1322a9
  1. 15
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  2. 6
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  3. 141
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  4. 117
      tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs
  5. 4
      tests/Images/Input/Png/PngWithMetaData.png

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

@ -272,6 +272,21 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngChunkType.Text:
this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
break;
case PngChunkType.CompressedText:
this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
break;
case PngChunkType.InternationalText:
this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
break;
case PngChunkType.Exif:
if (!this.ignoreMetadata)
{
var 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;

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

@ -149,10 +149,10 @@ namespace SixLabors.ImageSharp.Formats.Png
stream.Write(PngConstants.HeaderBytes);
this.WriteHeaderChunk(stream);
this.WriteGammaChunk(stream);
this.WritePaletteChunk(stream, quantized);
this.WriteTransparencyChunk(stream, pngMetadata);
this.WritePhysicalChunk(stream, metadata);
this.WriteGammaChunk(stream);
this.WriteExifChunk(stream, metadata);
this.WriteTextChunks(stream, pngMetadata);
this.WriteDataChunks(image.Frames.RootFrame, quantized, stream);
@ -538,6 +538,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Writes the palette chunk to the stream.
/// Should be written before the first IDAT chunk.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
@ -595,6 +596,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Writes the physical dimension information to the stream.
/// Should be written before IDAT chunk.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="meta">The image metadata.</param>
@ -716,6 +718,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Writes the gamma information to the stream.
/// Should be written before PLTE and IDAT chunk.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
private void WriteGammaChunk(Stream stream)
@ -733,6 +736,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Writes the transparency chunk to the stream.
/// Should be written after PLTE and before IDAT.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="pngMetadata">The image metadata.</param>

141
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0.
// ReSharper disable InconsistentNaming
using System;
using System.Buffers.Binary;
using System.IO;
using System.Linq;
@ -18,6 +20,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
{
public class PngEncoderTests
{
private static PngEncoder PngEncoder => new PngEncoder();
public static readonly TheoryData<string, PngBitDepth> PngBitDepthFiles =
new TheoryData<string, PngBitDepth>
{
@ -234,8 +238,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
{
using (Stream stream = new MemoryStream())
{
var encoder = new PngEncoder();
encoder.Encode(provider.GetImage(), stream);
PngEncoder.Encode(provider.GetImage(), stream);
stream.Seek(0, SeekOrigin.Begin);
@ -281,7 +284,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
using (Image<TPixel> image = provider.GetImage())
using (var ms = new MemoryStream())
{
image.Save(ms, new PngEncoder());
image.Save(ms, PngEncoder);
byte[] data = ms.ToArray().Take(8).ToArray();
byte[] expected =
@ -304,14 +307,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
[MemberData(nameof(RatioFiles))]
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var options = new PngEncoder();
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, options);
input.Save(memStream, PngEncoder);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
@ -329,14 +330,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
[MemberData(nameof(PngBitDepthFiles))]
public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth)
{
var options = new PngEncoder();
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, options);
input.Save(memStream, PngEncoder);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
@ -353,8 +352,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
[MemberData(nameof(PngTrnsFiles))]
public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType)
{
var options = new PngEncoder();
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
{
@ -363,7 +360,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
using (var memStream = new MemoryStream())
{
input.Save(memStream, options);
input.Save(memStream, PngEncoder);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
@ -404,6 +401,126 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
}
}
[Fact]
public void HeaderChunk_ComesFirst()
{
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
// Skip header.
Span<byte> bytesSpan = memStream.ToArray().AsSpan(8);
BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
Assert.Equal(PngChunkType.Header, type);
}
[Fact]
public void EndChunk_IsLast()
{
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
// Skip header.
Span<byte> bytesSpan = memStream.ToArray().AsSpan(8);
bool endChunkFound = false;
while (bytesSpan.Length > 0)
{
int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
Assert.False(endChunkFound);
if (type == PngChunkType.End)
{
endChunkFound = true;
}
bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
}
}
[Theory]
[InlineData(PngChunkType.Gamma)]
[InlineData(PngChunkType.Chroma)]
[InlineData(PngChunkType.EmbeddedColorProfile)]
[InlineData(PngChunkType.SignificantBits)]
[InlineData(PngChunkType.StandardRgbColourSpace)]
public void Chunk_ComesBeforePlteAndIDat(object chunkTypeObj)
{
var chunkType = (PngChunkType)chunkTypeObj;
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
// Skip header.
Span<byte> bytesSpan = memStream.ToArray().AsSpan(8);
bool palFound = false;
bool dataFound = false;
while (bytesSpan.Length > 0)
{
int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
if (chunkType == type)
{
Assert.False(palFound || dataFound, $"{chunkType} chunk should come before data and palette chunk");
}
switch (type)
{
case PngChunkType.Data:
dataFound = true;
break;
case PngChunkType.Palette:
palFound = true;
break;
}
bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
}
}
[Theory]
[InlineData(PngChunkType.Physical)]
[InlineData(PngChunkType.SuggestedPalette)]
public void Chunk_ComesBeforeIDat(object chunkTypeObj)
{
var chunkType = (PngChunkType)chunkTypeObj;
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
// Skip header.
Span<byte> bytesSpan = memStream.ToArray().AsSpan(8);
bool dataFound = false;
while (bytesSpan.Length > 0)
{
int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
if (chunkType == type)
{
Assert.False(dataFound, $"{chunkType} chunk should come before data chunk");
}
if (type == PngChunkType.Data)
{
dataFound = true;
}
bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
}
}
[Theory]
[WithTestPatternImages(587, 821, PixelTypes.Rgba32)]
[WithTestPatternImages(677, 683, PixelTypes.Rgba32)]

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

@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
@ -56,17 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
using (Image<TPixel> image = provider.GetImage(new PngDecoder()))
{
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
Assert.Contains(meta.TextData, m => m.Keyword.Equals("Comment") && m.Value.Equals("comment"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("Author") && m.Value.Equals("ImageSharp"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("Copyright") && m.Value.Equals("ImageSharp"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("Title") && m.Value.Equals("unittest"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("Description") && m.Value.Equals("compressed-text"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("International") && m.Value.Equals("'e', mu'tlheghvam, ghaH yu'") && m.LanguageTag.Equals("x-klingon") && m.TranslatedKeyword.Equals("warning"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("International2") && m.Value.Equals("ИМАГЕШАРП") && m.LanguageTag.Equals("rus"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational") && m.Value.Equals("la plume de la mante") && m.LanguageTag.Equals("fra") && m.TranslatedKeyword.Equals("foobar"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational2") && m.Value.Equals("這是一個考驗") && m.LanguageTag.Equals("chinese"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoLang") && m.Value.Equals("this text chunk is missing a language tag"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoTranslatedKeyword") && m.Value.Equals("dieser chunk hat kein übersetztes Schlüßelwort"));
VerifyTextDataIsPresent(meta);
}
}
@ -85,17 +76,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, memoryStream))
{
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
Assert.Contains(meta.TextData, m => m.Keyword.Equals("Comment") && m.Value.Equals("comment"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("Author") && m.Value.Equals("ImageSharp"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("Copyright") && m.Value.Equals("ImageSharp"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("Title") && m.Value.Equals("unittest"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("Description") && m.Value.Equals("compressed-text"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("International") && m.Value.Equals("'e', mu'tlheghvam, ghaH yu'") && m.LanguageTag.Equals("x-klingon") && m.TranslatedKeyword.Equals("warning"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("International2") && m.Value.Equals("ИМАГЕШАРП") && m.LanguageTag.Equals("rus"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational") && m.Value.Equals("la plume de la mante") && m.LanguageTag.Equals("fra") && m.TranslatedKeyword.Equals("foobar"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational2") && m.Value.Equals("這是一個考驗") && m.LanguageTag.Equals("chinese"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoLang") && m.Value.Equals("this text chunk is missing a language tag"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoTranslatedKeyword") && m.Value.Equals("dieser chunk hat kein übersetztes Schlüßelwort"));
VerifyTextDataIsPresent(meta);
}
}
}
@ -149,6 +130,40 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
}
}
[Theory]
[WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)]
public void Decode_ReadsExifData<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoder
{
IgnoreMetadata = false
};
using (Image<TPixel> image = provider.GetImage(decoder))
{
Assert.NotNull(image.Metadata.ExifProfile);
ExifProfile exif = image.Metadata.ExifProfile;
VerifyExifDataIsPresent(exif);
}
}
[Theory]
[WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)]
public void Decode_IgnoresExifData_WhenIgnoreMetadataIsTrue<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoder
{
IgnoreMetadata = true
};
using (Image<TPixel> image = provider.GetImage(decoder))
{
Assert.Null(image.Metadata.ExifProfile);
}
}
[Fact]
public void Decode_IgnoreMetadataIsFalse_TextChunkIsRead()
{
@ -178,7 +193,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
IgnoreMetadata = true
};
var testFile = TestFile.Create(TestImages.Png.Blur);
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using (Image<Rgba32> image = testFile.CreateRgba32Image(options))
{
@ -220,5 +235,61 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
[Theory]
[InlineData(TestImages.Png.PngWithMetadata)]
public void Identify_ReadsTextData(string imagePath)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance);
VerifyTextDataIsPresent(meta);
}
}
[Theory]
[InlineData(TestImages.Png.PngWithMetadata)]
public void Identify_ReadsExifData(string imagePath)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo.Metadata.ExifProfile);
ExifProfile exif = imageInfo.Metadata.ExifProfile;
VerifyExifDataIsPresent(exif);
}
}
private static void VerifyExifDataIsPresent(ExifProfile exif)
{
Assert.Equal(1, exif.Values.Count);
IExifValue<string> software = exif.GetValue(ExifTag.Software);
Assert.NotNull(software);
Assert.Equal("ImageSharp", software.Value);
}
private static void VerifyTextDataIsPresent(PngMetadata meta)
{
Assert.NotNull(meta);
Assert.Contains(meta.TextData, m => m.Keyword.Equals("Comment") && m.Value.Equals("comment"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("Author") && m.Value.Equals("ImageSharp"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("Copyright") && m.Value.Equals("ImageSharp"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("Title") && m.Value.Equals("unittest"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("Description") && m.Value.Equals("compressed-text"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("International") && m.Value.Equals("'e', mu'tlheghvam, ghaH yu'") &&
m.LanguageTag.Equals("x-klingon") && m.TranslatedKeyword.Equals("warning"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("International2") && m.Value.Equals("ИМАГЕШАРП") && m.LanguageTag.Equals("rus"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational") && m.Value.Equals("la plume de la mante") &&
m.LanguageTag.Equals("fra") && m.TranslatedKeyword.Equals("foobar"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("CompressedInternational2") && m.Value.Equals("這是一個考驗") &&
m.LanguageTag.Equals("chinese"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoLang") && m.Value.Equals("this text chunk is missing a language tag"));
Assert.Contains(meta.TextData, m => m.Keyword.Equals("NoTranslatedKeyword") && m.Value.Equals("dieser chunk hat kein übersetztes Schlüßelwort"));
}
}
}

4
tests/Images/Input/Png/PngWithMetaData.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c0490f627b22a3487b78e2797ebb65f5741fdbabfd4a3d9db806ca624f62fe8c
size 805
oid sha256:a37d2d31c2148b94bfd732c8964808dcc2dcdb6d2c187bb5d0403dc09af9ab46
size 60544

Loading…
Cancel
Save