mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
15 changed files with 1294 additions and 452 deletions
@ -0,0 +1,238 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
// TODO: This could be useful elsewhere.
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components |
|||
{ |
|||
/// <summary>
|
|||
/// A stream reader that add a secondary level buffer in addition to native stream buffered reading
|
|||
/// to reduce the overhead of small incremental reads.
|
|||
/// </summary>
|
|||
internal class DoubleBufferedStreamReader : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The length, in bytes, of the buffering chunk
|
|||
/// </summary>
|
|||
public const int ChunkLength = 4096; |
|||
|
|||
private const int ChunkLengthMinusOne = ChunkLength - 1; |
|||
|
|||
private readonly Stream stream; |
|||
|
|||
private readonly IManagedByteBuffer managedBuffer; |
|||
|
|||
private readonly byte[] bufferChunk; |
|||
|
|||
private readonly int length; |
|||
|
|||
private int bytesRead; |
|||
|
|||
private int position; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DoubleBufferedStreamReader"/> class.
|
|||
/// </summary>
|
|||
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
|
|||
/// <param name="stream">The input stream.</param>
|
|||
public DoubleBufferedStreamReader(MemoryManager memoryManager, Stream stream) |
|||
{ |
|||
this.stream = stream; |
|||
this.length = (int)stream.Length; |
|||
this.managedBuffer = memoryManager.AllocateCleanManagedByteBuffer(ChunkLength); |
|||
this.bufferChunk = this.managedBuffer.Array; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the length, in bytes, of the stream
|
|||
/// </summary>
|
|||
public long Length => this.length; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current position within the stream
|
|||
/// </summary>
|
|||
public long Position |
|||
{ |
|||
get => this.position; |
|||
|
|||
set |
|||
{ |
|||
// Reset everything. It's easier than tracking.
|
|||
this.position = (int)value; |
|||
this.stream.Seek(this.position, SeekOrigin.Begin); |
|||
this.bytesRead = ChunkLength; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a byte from the stream and advances the position within the stream by one
|
|||
/// byte, or returns -1 if at the end of the stream.
|
|||
/// </summary>
|
|||
/// <returns>The unsigned byte cast to an <see cref="int"/>, or -1 if at the end of the stream.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public int ReadByte() |
|||
{ |
|||
if (this.position >= this.length) |
|||
{ |
|||
return -1; |
|||
} |
|||
|
|||
if (this.position == 0 || this.bytesRead > ChunkLengthMinusOne) |
|||
{ |
|||
return this.ReadByteSlow(); |
|||
} |
|||
|
|||
this.position++; |
|||
return this.bufferChunk[this.bytesRead++]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Skips the number of bytes in the stream
|
|||
/// </summary>
|
|||
/// <param name="count">The number of bytes to skip</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Skip(int count) |
|||
{ |
|||
this.Position += count; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a sequence of bytes from the current stream and advances the position within the stream
|
|||
/// by the number of bytes read.
|
|||
/// </summary>
|
|||
/// <param name="buffer">
|
|||
/// An array of bytes. When this method returns, the buffer contains the specified
|
|||
/// byte array with the values between offset and (offset + count - 1) replaced by
|
|||
/// the bytes read from the current source.
|
|||
/// </param>
|
|||
/// <param name="offset">
|
|||
/// The zero-based byte offset in buffer at which to begin storing the data read
|
|||
/// from the current stream.
|
|||
/// </param>
|
|||
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
|
|||
/// <returns>
|
|||
/// The total number of bytes read into the buffer. This can be less than the number
|
|||
/// of bytes requested if that many bytes are not currently available, or zero (0)
|
|||
/// if the end of the stream has been reached.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public int Read(byte[] buffer, int offset, int count) |
|||
{ |
|||
if (buffer.Length > ChunkLength) |
|||
{ |
|||
return this.ReadToBufferSlow(buffer, offset, count); |
|||
} |
|||
|
|||
if (this.position == 0 || count + this.bytesRead > ChunkLength) |
|||
{ |
|||
return this.ReadToChunkSlow(buffer, offset, count); |
|||
} |
|||
|
|||
int n = this.GetCount(count); |
|||
this.CopyBytes(buffer, offset, n); |
|||
|
|||
this.position += n; |
|||
this.bytesRead += n; |
|||
|
|||
return n; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
this.managedBuffer?.Dispose(); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
private int ReadByteSlow() |
|||
{ |
|||
if (this.position != this.stream.Position) |
|||
{ |
|||
this.stream.Seek(this.position, SeekOrigin.Begin); |
|||
} |
|||
|
|||
this.stream.Read(this.bufferChunk, 0, ChunkLength); |
|||
this.bytesRead = 0; |
|||
|
|||
this.position++; |
|||
return this.bufferChunk[this.bytesRead++]; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
private int ReadToChunkSlow(byte[] buffer, int offset, int count) |
|||
{ |
|||
// Refill our buffer then copy.
|
|||
if (this.position != this.stream.Position) |
|||
{ |
|||
this.stream.Seek(this.position, SeekOrigin.Begin); |
|||
} |
|||
|
|||
this.stream.Read(this.bufferChunk, 0, ChunkLength); |
|||
this.bytesRead = 0; |
|||
|
|||
int n = this.GetCount(count); |
|||
this.CopyBytes(buffer, offset, n); |
|||
|
|||
this.position += n; |
|||
this.bytesRead += n; |
|||
|
|||
return n; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
private int ReadToBufferSlow(byte[] buffer, int offset, int count) |
|||
{ |
|||
// Read to target but don't copy to our chunk.
|
|||
if (this.position != this.stream.Position) |
|||
{ |
|||
this.stream.Seek(this.position, SeekOrigin.Begin); |
|||
} |
|||
|
|||
int n = this.stream.Read(buffer, offset, count); |
|||
this.Position += n; |
|||
return n; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private int GetCount(int count) |
|||
{ |
|||
int n = this.length - this.position; |
|||
if (n > count) |
|||
{ |
|||
n = count; |
|||
} |
|||
|
|||
if (n < 0) |
|||
{ |
|||
n = 0; |
|||
} |
|||
|
|||
return n; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void CopyBytes(byte[] buffer, int offset, int count) |
|||
{ |
|||
if (count < 9) |
|||
{ |
|||
int byteCount = count; |
|||
int read = this.bytesRead; |
|||
byte[] chunk = this.bufferChunk; |
|||
|
|||
while (--byteCount > -1) |
|||
{ |
|||
buffer[offset + byteCount] = chunk[read + byteCount]; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
Buffer.BlockCopy(this.bufferChunk, this.bytesRead, buffer, offset, count); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,114 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg |
|||
{ |
|||
[Config(typeof(Config.ShortClr))] |
|||
public class DoubleBufferedStreams |
|||
{ |
|||
private byte[] buffer = CreateTestBytes(); |
|||
private byte[] chunk1 = new byte[2]; |
|||
private byte[] chunk2 = new byte[2]; |
|||
|
|||
private MemoryStream stream1; |
|||
private MemoryStream stream2; |
|||
private MemoryStream stream3; |
|||
private MemoryStream stream4; |
|||
DoubleBufferedStreamReader reader1; |
|||
DoubleBufferedStreamReader reader2; |
|||
|
|||
[GlobalSetup] |
|||
public void CreateStreams() |
|||
{ |
|||
this.stream1 = new MemoryStream(this.buffer); |
|||
this.stream2 = new MemoryStream(this.buffer); |
|||
this.stream3 = new MemoryStream(this.buffer); |
|||
this.stream4 = new MemoryStream(this.buffer); |
|||
this.reader1 = new DoubleBufferedStreamReader(Configuration.Default.MemoryManager, this.stream2); |
|||
this.reader2 = new DoubleBufferedStreamReader(Configuration.Default.MemoryManager, this.stream2); |
|||
} |
|||
|
|||
[GlobalCleanup] |
|||
public void DestroyStreams() |
|||
{ |
|||
this.stream1?.Dispose(); |
|||
this.stream2?.Dispose(); |
|||
this.stream3?.Dispose(); |
|||
this.stream4?.Dispose(); |
|||
this.reader1?.Dispose(); |
|||
this.reader2?.Dispose(); |
|||
} |
|||
|
|||
[Benchmark(Baseline = true)] |
|||
public int StandardStreamReadByte() |
|||
{ |
|||
int r = 0; |
|||
Stream stream = this.stream1; |
|||
|
|||
for (int i = 0; i < stream.Length; i++) |
|||
{ |
|||
r += stream.ReadByte(); |
|||
} |
|||
|
|||
return r; |
|||
} |
|||
|
|||
[Benchmark] |
|||
public int StandardStreamRead() |
|||
{ |
|||
int r = 0; |
|||
Stream stream = this.stream1; |
|||
byte[] b = this.chunk1; |
|||
|
|||
for (int i = 0; i < stream.Length / 2; i++) |
|||
{ |
|||
r += stream.Read(b, 0, 2); |
|||
} |
|||
|
|||
return r; |
|||
} |
|||
|
|||
[Benchmark] |
|||
public int DoubleBufferedStreamReadByte() |
|||
{ |
|||
int r = 0; |
|||
DoubleBufferedStreamReader reader = this.reader1; |
|||
|
|||
for (int i = 0; i < reader.Length; i++) |
|||
{ |
|||
r += reader.ReadByte(); |
|||
} |
|||
|
|||
return r; |
|||
} |
|||
|
|||
[Benchmark] |
|||
public int DoubleBufferedStreamRead() |
|||
{ |
|||
int r = 0; |
|||
DoubleBufferedStreamReader reader = this.reader2; |
|||
byte[] b = this.chunk2; |
|||
|
|||
for (int i = 0; i < reader.Length / 2; i++) |
|||
{ |
|||
r += reader.Read(b, 0, 2); |
|||
} |
|||
|
|||
return r; |
|||
} |
|||
|
|||
private static byte[] CreateTestBytes() |
|||
{ |
|||
byte[] buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3]; |
|||
var random = new Random(); |
|||
random.NextBytes(buffer); |
|||
|
|||
return buffer; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; |
|||
using SixLabors.ImageSharp.Tests; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg |
|||
{ |
|||
[Config(typeof(Config.ShortClr))] |
|||
public class IdentifyJpeg |
|||
{ |
|||
private byte[] jpegBytes; |
|||
|
|||
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); |
|||
|
|||
[Params(TestImages.Jpeg.Baseline.Jpeg420Exif, TestImages.Jpeg.Baseline.Calliphora)] |
|||
public string TestImage { get; set; } |
|||
|
|||
[GlobalSetup] |
|||
public void ReadImages() |
|||
{ |
|||
if (this.jpegBytes == null) |
|||
{ |
|||
this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public IImageInfo IdentifyGolang() |
|||
{ |
|||
using (var memoryStream = new MemoryStream(this.jpegBytes)) |
|||
{ |
|||
var decoder = new OrigJpegDecoder(); |
|||
|
|||
return decoder.Identify(Configuration.Default, memoryStream); |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public IImageInfo IdentifyPdfJs() |
|||
{ |
|||
using (var memoryStream = new MemoryStream(this.jpegBytes)) |
|||
{ |
|||
var decoder = new PdfJsJpegDecoder(); |
|||
return decoder.Identify(Configuration.Default, memoryStream); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,159 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Jpg |
|||
{ |
|||
public class DoubleBufferedStreamReaderTests |
|||
{ |
|||
private readonly MemoryManager manager = Configuration.Default.MemoryManager; |
|||
|
|||
[Fact] |
|||
public void DoubleBufferedStreamReaderCanReadSingleByteFromOrigin() |
|||
{ |
|||
using (MemoryStream stream = this.CreateTestStream()) |
|||
{ |
|||
byte[] expected = stream.ToArray(); |
|||
var reader = new DoubleBufferedStreamReader(this.manager, stream); |
|||
|
|||
Assert.Equal(expected[0], reader.ReadByte()); |
|||
|
|||
// We've read a whole chunk but increment by 1 in our reader.
|
|||
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength); |
|||
Assert.Equal(1, reader.Position); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void DoubleBufferedStreamReaderCanReadSubsequentSingleByteCorrectly() |
|||
{ |
|||
using (MemoryStream stream = this.CreateTestStream()) |
|||
{ |
|||
byte[] expected = stream.ToArray(); |
|||
var reader = new DoubleBufferedStreamReader(this.manager, stream); |
|||
|
|||
for (int i = 0; i < expected.Length; i++) |
|||
{ |
|||
Assert.Equal(expected[i], reader.ReadByte()); |
|||
Assert.Equal(i + 1, reader.Position); |
|||
|
|||
if (i < DoubleBufferedStreamReader.ChunkLength) |
|||
{ |
|||
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength); |
|||
} |
|||
else if (i >= DoubleBufferedStreamReader.ChunkLength && i < DoubleBufferedStreamReader.ChunkLength * 2) |
|||
{ |
|||
// We should have advanced to the second chunk now.
|
|||
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 2); |
|||
} |
|||
else |
|||
{ |
|||
// We should have advanced to the third chunk now.
|
|||
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 3); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void DoubleBufferedStreamReaderCanReadMultipleBytesFromOrigin() |
|||
{ |
|||
using (MemoryStream stream = this.CreateTestStream()) |
|||
{ |
|||
byte[] buffer = new byte[2]; |
|||
byte[] expected = stream.ToArray(); |
|||
var reader = new DoubleBufferedStreamReader(this.manager, stream); |
|||
|
|||
Assert.Equal(2, reader.Read(buffer, 0, 2)); |
|||
Assert.Equal(expected[0], buffer[0]); |
|||
Assert.Equal(expected[1], buffer[1]); |
|||
|
|||
// We've read a whole chunk but increment by the buffer length in our reader.
|
|||
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength); |
|||
Assert.Equal(buffer.Length, reader.Position); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void DoubleBufferedStreamReaderCanReadSubsequentMultipleByteCorrectly() |
|||
{ |
|||
using (MemoryStream stream = this.CreateTestStream()) |
|||
{ |
|||
byte[] buffer = new byte[2]; |
|||
byte[] expected = stream.ToArray(); |
|||
var reader = new DoubleBufferedStreamReader(this.manager, stream); |
|||
|
|||
for (int i = 0, o = 0; i < expected.Length / 2; i++, o += 2) |
|||
{ |
|||
|
|||
Assert.Equal(2, reader.Read(buffer, 0, 2)); |
|||
Assert.Equal(expected[o], buffer[0]); |
|||
Assert.Equal(expected[o + 1], buffer[1]); |
|||
Assert.Equal(o + 2, reader.Position); |
|||
|
|||
int offset = i * 2; |
|||
if (offset < DoubleBufferedStreamReader.ChunkLength) |
|||
{ |
|||
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength); |
|||
} |
|||
else if (offset >= DoubleBufferedStreamReader.ChunkLength && offset < DoubleBufferedStreamReader.ChunkLength * 2) |
|||
{ |
|||
// We should have advanced to the second chunk now.
|
|||
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 2); |
|||
} |
|||
else |
|||
{ |
|||
// We should have advanced to the third chunk now.
|
|||
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 3); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void DoubleBufferedStreamReaderCanSkip() |
|||
{ |
|||
using (MemoryStream stream = this.CreateTestStream()) |
|||
{ |
|||
byte[] expected = stream.ToArray(); |
|||
var reader = new DoubleBufferedStreamReader(this.manager, stream); |
|||
|
|||
int skip = 50; |
|||
int plusOne = 1; |
|||
int skip2 = DoubleBufferedStreamReader.ChunkLength; |
|||
|
|||
// Skip
|
|||
reader.Skip(skip); |
|||
Assert.Equal(skip, reader.Position); |
|||
Assert.Equal(stream.Position, reader.Position); |
|||
|
|||
// Read
|
|||
Assert.Equal(expected[skip], reader.ReadByte()); |
|||
|
|||
// Skip Again
|
|||
reader.Skip(skip2); |
|||
|
|||
// First Skap + First Read + Second Skip
|
|||
int position = skip + plusOne + skip2; |
|||
|
|||
Assert.Equal(position, reader.Position); |
|||
Assert.Equal(stream.Position, reader.Position); |
|||
Assert.Equal(expected[position], reader.ReadByte()); |
|||
} |
|||
} |
|||
|
|||
private MemoryStream CreateTestStream() |
|||
{ |
|||
byte[] buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3]; |
|||
var random = new Random(); |
|||
random.NextBytes(buffer); |
|||
|
|||
return new MemoryStream(buffer); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,158 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.Formats; |
|||
using SixLabors.ImageSharp.MetaData.Profiles.Exif; |
|||
using SixLabors.ImageSharp.MetaData.Profiles.Icc; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using Xunit; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Jpg |
|||
{ |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
using SixLabors.ImageSharp.Formats.Jpeg; |
|||
|
|||
public partial class JpegDecoderTests |
|||
{ |
|||
// TODO: A JPEGsnoop & metadata expert should review if the Exif/Icc expectations are correct.
|
|||
// I'm seeing several entries with Exif-related names in images where we do not decode an exif profile. (- Anton)
|
|||
public static readonly TheoryData<bool, string, int, bool, bool> MetaDataTestData = |
|||
new TheoryData<bool, string, int, bool, bool> |
|||
{ |
|||
{ false, TestImages.Jpeg.Progressive.Progress, 24, false, false }, |
|||
{ false, TestImages.Jpeg.Progressive.Fb, 24, false, true }, |
|||
{ false, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, |
|||
{ false, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, |
|||
{ false, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, |
|||
{ false, TestImages.Jpeg.Baseline.Snake, 24, true, true }, |
|||
{ false, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, |
|||
|
|||
{ true, TestImages.Jpeg.Progressive.Progress, 24, false, false }, |
|||
{ true, TestImages.Jpeg.Progressive.Fb, 24, false, true }, |
|||
{ true, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, |
|||
{ true, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, |
|||
{ true, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, |
|||
{ true, TestImages.Jpeg.Baseline.Snake, 24, true, true }, |
|||
{ true, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, |
|||
}; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(MetaDataTestData))] |
|||
public void MetaDataIsParsedCorrectly_Orig( |
|||
bool useIdentify, |
|||
string imagePath, |
|||
int expectedPixelSize, |
|||
bool exifProfilePresent, |
|||
bool iccProfilePresent) |
|||
{ |
|||
TestMetaDataImpl( |
|||
useIdentify, |
|||
OrigJpegDecoder, |
|||
imagePath, |
|||
expectedPixelSize, |
|||
exifProfilePresent, |
|||
iccProfilePresent); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(MetaDataTestData))] |
|||
public void MetaDataIsParsedCorrectly_PdfJs( |
|||
bool useIdentify, |
|||
string imagePath, |
|||
int expectedPixelSize, |
|||
bool exifProfilePresent, |
|||
bool iccProfilePresent) |
|||
{ |
|||
TestMetaDataImpl( |
|||
useIdentify, |
|||
PdfJsJpegDecoder, |
|||
imagePath, |
|||
expectedPixelSize, |
|||
exifProfilePresent, |
|||
iccProfilePresent); |
|||
} |
|||
|
|||
private static void TestMetaDataImpl( |
|||
bool useIdentify, |
|||
IImageDecoder decoder, |
|||
string imagePath, |
|||
int expectedPixelSize, |
|||
bool exifProfilePresent, |
|||
bool iccProfilePresent) |
|||
{ |
|||
var testFile = TestFile.Create(imagePath); |
|||
using (var stream = new MemoryStream(testFile.Bytes, false)) |
|||
{ |
|||
IImageInfo imageInfo = useIdentify |
|||
? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream) |
|||
: decoder.Decode<Rgba32>(Configuration.Default, stream); |
|||
|
|||
Assert.NotNull(imageInfo); |
|||
Assert.NotNull(imageInfo.PixelType); |
|||
|
|||
if (useIdentify) |
|||
{ |
|||
Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel); |
|||
} |
|||
else |
|||
{ |
|||
// When full Image<TPixel> decoding is performed, BitsPerPixel will match TPixel
|
|||
int bpp32 = Unsafe.SizeOf<Rgba32>() * 8; |
|||
Assert.Equal(bpp32, imageInfo.PixelType.BitsPerPixel); |
|||
} |
|||
|
|||
ExifProfile exifProfile = imageInfo.MetaData.ExifProfile; |
|||
|
|||
if (exifProfilePresent) |
|||
{ |
|||
Assert.NotNull(exifProfile); |
|||
Assert.NotEmpty(exifProfile.Values); |
|||
} |
|||
else |
|||
{ |
|||
Assert.Null(exifProfile); |
|||
} |
|||
|
|||
IccProfile iccProfile = imageInfo.MetaData.IccProfile; |
|||
|
|||
if (iccProfilePresent) |
|||
{ |
|||
Assert.NotNull(iccProfile); |
|||
Assert.NotEmpty(iccProfile.Entries); |
|||
} |
|||
else |
|||
{ |
|||
Assert.Null(iccProfile); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false)] |
|||
[InlineData(true)] |
|||
public void IgnoreMetaData_ControlsWhetherMetaDataIsParsed(bool ignoreMetaData) |
|||
{ |
|||
var decoder = new JpegDecoder() { IgnoreMetadata = ignoreMetaData }; |
|||
|
|||
// Snake.jpg has both Exif and ICC profiles defined:
|
|||
var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Snake); |
|||
|
|||
using (Image<Rgba32> image = testFile.CreateImage(decoder)) |
|||
{ |
|||
if (ignoreMetaData) |
|||
{ |
|||
Assert.Null(image.MetaData.ExifProfile); |
|||
Assert.Null(image.MetaData.IccProfile); |
|||
} |
|||
else |
|||
{ |
|||
Assert.NotNull(image.MetaData.ExifProfile); |
|||
Assert.NotNull(image.MetaData.IccProfile); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue