Browse Source

Merge branch 'master' into ImageSharp-issue-711-resize-aspect-ratio-or-closest

pull/719/head
Davidsv 8 years ago
committed by GitHub
parent
commit
75f2e656aa
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 34
      src/ImageSharp/Common/Extensions/EncoderExtensions.cs
  2. 37
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs
  3. 24
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer256.cs
  4. 24
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer512.cs
  5. 24
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt16Buffer257.cs
  6. 24
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt32Buffer18.cs
  7. 24
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedUInt32Buffer18.cs
  8. 115
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
  9. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
  10. 34
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs
  11. 17
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  12. 107
      src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs
  13. 12
      src/ImageSharp/Formats/Png/PngConstants.cs
  14. 164
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  15. 44
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  16. 30
      src/ImageSharp/Formats/Png/PngHeader.cs
  17. 13
      src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs
  18. 50
      src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.cs
  19. 235
      src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.tt
  20. 2
      src/ImageSharp/Processing/CropExtensions.cs
  21. 7
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
  22. 2
      src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs
  23. 8
      tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs
  24. 8
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
  25. 13
      tests/ImageSharp.Tests/ImageOperationTests.cs
  26. 1
      tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs
  27. 8
      tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs
  28. 4
      tests/ImageSharp.Tests/TestImages.cs
  29. 2
      tests/Images/External
  30. BIN
      tests/Images/Input/Jpg/issues/Issue721-InvalidAPP0.jpg
  31. BIN
      tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-A.jpg
  32. BIN
      tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-B.jpg
  33. BIN
      tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-C.jpg

34
src/ImageSharp/Common/Extensions/EncoderExtensions.cs

@ -0,0 +1,34 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
#if !NETCOREAPP2_1
using System;
using System.Text;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods for the <see cref="Encoder"/> type.
/// </summary>
internal static unsafe class EncoderExtensions
{
/// <summary>
/// Gets a string from the provided buffer data.
/// </summary>
/// <param name="encoding">The encoding.</param>
/// <param name="buffer">The buffer.</param>
/// <returns>The string.</returns>
public static string GetString(this Encoding encoding, ReadOnlySpan<byte> buffer)
{
#if NETSTANDARD1_1
return encoding.GetString(buffer.ToArray());
#else
fixed (byte* bytes = buffer)
{
return encoding.GetString(bytes, buffer.Length);
}
#endif
}
}
}
#endif

37
src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs

@ -20,54 +20,47 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// Initializes a new instance of the <see cref="FastACTables"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator used to allocate memory for image processing operations.</param>
public FastACTables(MemoryAllocator memoryAllocator)
{
this.tables = memoryAllocator.Allocate2D<short>(512, 4, AllocationOptions.Clean);
}
public FastACTables(MemoryAllocator memoryAllocator) => this.tables = memoryAllocator.Allocate2D<short>(512, 4, AllocationOptions.Clean);
/// <summary>
/// Gets the <see cref="Span{Int16}"/> representing the table at the index in the collection.
/// </summary>
/// <param name="index">The table index.</param>
/// <returns><see cref="Span{Int16}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<short> GetTableSpan(int index)
{
return this.tables.GetRowSpan(index);
}
[MethodImpl(InliningOptions.ShortMethod)]
public ReadOnlySpan<short> GetTableSpan(int index) => this.tables.GetRowSpan(index);
/// <summary>
/// Gets a reference to the first element of the AC table indexed by <see cref="JpegComponent.ACHuffmanTableId"/> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref short GetAcTableReference(JpegComponent component)
{
return ref this.tables.GetRowSpan(component.ACHuffmanTableId)[0];
}
/// Gets a reference to the first element of the AC table indexed by <see cref="JpegComponent.ACHuffmanTableId"/>
/// </summary>
/// <param name="component">The frame component.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public ref short GetAcTableReference(JpegComponent component) => ref this.tables.GetRowSpan(component.ACHuffmanTableId)[0];
/// <summary>
/// Builds a lookup table for fast AC entropy scan decoding.
/// </summary>
/// <param name="index">The table index.</param>
/// <param name="acHuffmanTables">The collection of AC Huffman tables.</param>
public void BuildACTableLut(int index, HuffmanTables acHuffmanTables)
public unsafe void BuildACTableLut(int index, HuffmanTables acHuffmanTables)
{
const int FastBits = ScanDecoder.FastBits;
Span<short> fastAC = this.tables.GetRowSpan(index);
ref HuffmanTable huffman = ref acHuffmanTables[index];
ref HuffmanTable huffmanTable = ref acHuffmanTables[index];
int i;
for (i = 0; i < (1 << FastBits); i++)
{
byte fast = huffman.Lookahead[i];
byte fast = huffmanTable.Lookahead[i];
fastAC[i] = 0;
if (fast < byte.MaxValue)
{
int rs = huffman.Values[fast];
int rs = huffmanTable.Values[fast];
int run = (rs >> 4) & 15;
int magbits = rs & 15;
int len = huffman.Sizes[fast];
int len = huffmanTable.Sizes[fast];
if (magbits > 0 && len + magbits <= FastBits)
if (magbits != 0 && len + magbits <= FastBits)
{
// Magnitude code followed by receive_extend code
int k = ((i << len) & ((1 << FastBits) - 1)) >> (FastBits - magbits);
@ -80,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// if the result is small enough, we can fit it in fastAC table
if (k >= -128 && k <= 127)
{
fastAC[i] = (short)((k * 256) + (run * 16) + (len + magbits));
fastAC[i] = (short)((k << 8) + (run << 4) + (len + magbits));
}
}
}

24
src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer256.cs

@ -1,24 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct FixedByteBuffer256
{
public fixed byte Data[256];
public byte this[int idx]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ref byte self = ref Unsafe.As<FixedByteBuffer256, byte>(ref this);
return Unsafe.Add(ref self, idx);
}
}
}
}

24
src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer512.cs

@ -1,24 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct FixedByteBuffer512
{
public fixed byte Data[1 << ScanDecoder.FastBits];
public byte this[int idx]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ref byte self = ref Unsafe.As<FixedByteBuffer512, byte>(ref this);
return Unsafe.Add(ref self, idx);
}
}
}
}

24
src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt16Buffer257.cs

@ -1,24 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct FixedInt16Buffer257
{
public fixed short Data[257];
public short this[int idx]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ref short self = ref Unsafe.As<FixedInt16Buffer257, short>(ref this);
return Unsafe.Add(ref self, idx);
}
}
}
}

24
src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt32Buffer18.cs

@ -1,24 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct FixedInt32Buffer18
{
public fixed int Data[18];
public int this[int idx]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ref int self = ref Unsafe.As<FixedInt32Buffer18, int>(ref this);
return Unsafe.Add(ref self, idx);
}
}
}
}

24
src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedUInt32Buffer18.cs

@ -1,24 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct FixedUInt32Buffer18
{
public fixed uint Data[18];
public uint this[int idx]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ref uint self = ref Unsafe.As<FixedUInt32Buffer18, uint>(ref this);
return Unsafe.Add(ref self, idx);
}
}
}
}

115
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs

@ -20,114 +20,105 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Gets the max code array
/// </summary>
public FixedUInt32Buffer18 MaxCode;
public fixed uint MaxCode[18];
/// <summary>
/// Gets the value offset array
/// </summary>
public FixedInt32Buffer18 ValOffset;
public fixed int ValOffset[18];
/// <summary>
/// Gets the huffman value array
/// </summary>
public FixedByteBuffer256 Values;
public fixed byte Values[256];
/// <summary>
/// Gets the lookahead array
/// </summary>
public FixedByteBuffer512 Lookahead;
public fixed byte Lookahead[512];
/// <summary>
/// Gets the sizes array
/// </summary>
public FixedInt16Buffer257 Sizes;
public fixed short Sizes[257];
/// <summary>
/// Initializes a new instance of the <see cref="HuffmanTable"/> struct.
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations.</param>
/// <param name="count">The code lengths</param>
/// <param name="codeLengths">The code lengths</param>
/// <param name="values">The huffman values</param>
public HuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan<byte> count, ReadOnlySpan<byte> values)
public HuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
{
const int Length = 257;
using (IMemoryOwner<short> huffcode = memoryAllocator.Allocate<short>(Length))
{
ref short huffcodeRef = ref MemoryMarshal.GetReference(huffcode.GetSpan());
ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengths);
// Figure C.1: make table of Huffman code length for each symbol
fixed (short* sizesRef = this.Sizes.Data)
ref short sizesRef = ref this.Sizes[0];
short x = 0;
for (short i = 1; i < 17; i++)
{
short x = 0;
for (short i = 1; i < 17; i++)
byte length = Unsafe.Add(ref codeLengthsRef, i);
for (short j = 0; j < length; j++)
{
byte l = count[i];
for (short j = 0; j < l; j++)
{
sizesRef[x] = i;
x++;
}
Unsafe.Add(ref sizesRef, x++) = i;
}
}
sizesRef[x] = 0;
Unsafe.Add(ref sizesRef, x) = 0;
// Figure C.2: generate the codes themselves
int k = 0;
fixed (int* valOffsetRef = this.ValOffset.Data)
fixed (uint* maxcodeRef = this.MaxCode.Data)
// Figure C.2: generate the codes themselves
int si = 0;
ref int valOffsetRef = ref this.ValOffset[0];
ref uint maxcodeRef = ref this.MaxCode[0];
uint code = 0;
int k;
for (k = 1; k < 17; k++)
{
// Compute delta to add to code to compute symbol id.
Unsafe.Add(ref valOffsetRef, k) = (int)(si - code);
if (Unsafe.Add(ref sizesRef, si) == k)
{
uint code = 0;
int j;
for (j = 1; j < 17; j++)
while (Unsafe.Add(ref sizesRef, si) == k)
{
// Compute delta to add to code to compute symbol id.
valOffsetRef[j] = (int)(k - code);
if (sizesRef[k] == j)
{
while (sizesRef[k] == j)
{
Unsafe.Add(ref huffcodeRef, k++) = (short)code++;
}
}
// Figure F.15: generate decoding tables for bit-sequential decoding.
// Compute largest code + 1 for this size. preshifted as need later.
maxcodeRef[j] = code << (16 - j);
code <<= 1;
Unsafe.Add(ref huffcodeRef, si++) = (short)code++;
}
maxcodeRef[j] = 0xFFFFFFFF;
}
// Generate non-spec lookup tables to speed up decoding.
fixed (byte* lookaheadRef = this.Lookahead.Data)
{
const int FastBits = ScanDecoder.FastBits;
var fast = new Span<byte>(lookaheadRef, 1 << FastBits);
fast.Fill(0xFF); // Flag for non-accelerated
// Figure F.15: generate decoding tables for bit-sequential decoding.
// Compute largest code + 1 for this size. preshifted as we needit later.
Unsafe.Add(ref maxcodeRef, k) = code << (16 - k);
code <<= 1;
}
Unsafe.Add(ref maxcodeRef, k) = 0xFFFFFFFF;
for (int i = 0; i < k; i++)
// Generate non-spec lookup tables to speed up decoding.
const int FastBits = ScanDecoder.FastBits;
ref byte fastRef = ref this.Lookahead[0];
Unsafe.InitBlockUnaligned(ref fastRef, 0xFF, 1 << FastBits); // Flag for non-accelerated
for (int i = 0; i < si; i++)
{
int size = Unsafe.Add(ref sizesRef, i);
if (size <= FastBits)
{
int c = Unsafe.Add(ref huffcodeRef, i) << (FastBits - size);
int m = 1 << (FastBits - size);
for (int l = 0; l < m; l++)
{
int s = sizesRef[i];
if (s <= ScanDecoder.FastBits)
{
int c = Unsafe.Add(ref huffcodeRef, i) << (FastBits - s);
int m = 1 << (FastBits - s);
for (int j = 0; j < m; j++)
{
fast[c + j] = (byte)i;
}
}
Unsafe.Add(ref fastRef, c + l) = (byte)i;
}
}
}
}
fixed (byte* huffValRef = this.Values.Data)
{
var huffValSpan = new Span<byte>(huffValRef, 256);
values.CopyTo(huffValSpan);
}
Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), 256);
}
}
}

6
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs

@ -45,6 +45,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
public byte[] ComponentIds { get; set; }
/// <summary>
/// Gets or sets the order in which to process the components
/// in interleaved mode.
/// </summary>
public byte[] ComponentOrder { get; set; }
/// <summary>
/// Gets or sets the frame component collection
/// </summary>

34
src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs

@ -34,9 +34,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// The restart interval.
private readonly int restartInterval;
// The current component index.
private readonly int componentIndex;
// The number of interleaved components.
private readonly int componentsLength;
@ -87,7 +84,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <param name="dcHuffmanTables">The DC Huffman tables.</param>
/// <param name="acHuffmanTables">The AC Huffman tables.</param>
/// <param name="fastACTables">The fast AC decoding tables.</param>
/// <param name="componentIndex">The component index within the array.</param>
/// <param name="componentsLength">The length of the components. Different to the array length.</param>
/// <param name="restartInterval">The reset interval.</param>
/// <param name="spectralStart">The spectral selection start.</param>
@ -100,7 +96,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
HuffmanTables dcHuffmanTables,
HuffmanTables acHuffmanTables,
FastACTables fastACTables,
int componentIndex,
int componentsLength,
int restartInterval,
int spectralStart,
@ -117,7 +112,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.components = frame.Components;
this.marker = JpegConstants.Markers.XFF;
this.markerPosition = 0;
this.componentIndex = componentIndex;
this.componentsLength = componentsLength;
this.restartInterval = restartInterval;
this.spectralStart = spectralStart;
@ -176,7 +170,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// Scan an interleaved mcu... process components in order
for (int k = 0; k < this.componentsLength; k++)
{
JpegComponent component = this.components[k];
int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
@ -223,14 +218,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
/// <summary>
/// Non-interleaved data, we just need to process one block at a ti
/// in trivial scanline order
/// number of blocks to do just depends on how many actual "pixels"
/// component has, independent of interleaved MCU blocking and such
/// Non-interleaved data, we just need to process one block at a time in trivial scanline order
/// number of blocks to do just depends on how many actual "pixels" each component has,
/// independent of interleaved MCU blocking and such.
/// </summary>
private void ParseBaselineDataNonInterleaved()
{
JpegComponent component = this.components[this.componentIndex];
JpegComponent component = this.components[this.frame.ComponentOrder[0]];
int w = component.WidthInBlocks;
int h = component.HeightInBlocks;
@ -295,7 +289,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// Scan an interleaved mcu... process components in order
for (int k = 0; k < this.componentsLength; k++)
{
JpegComponent component = this.components[k];
int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor;
@ -344,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
private void ParseProgressiveDataNonInterleaved()
{
JpegComponent component = this.components[this.componentIndex];
JpegComponent component = this.components[this.frame.ComponentOrder[0]];
int w = component.WidthInBlocks;
int h = component.HeightInBlocks;
@ -729,8 +724,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
uint k = LRot(this.codeBuffer, n);
this.codeBuffer = k & ~Bmask[n];
k &= Bmask[n];
uint mask = Bmask[n];
this.codeBuffer = k & ~mask;
k &= mask;
this.codeBits -= n;
return (int)k;
}
@ -804,7 +800,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
[MethodImpl(InliningOptions.ShortMethod)]
private int DecodeHuffman(ref HuffmanTable table)
private unsafe int DecodeHuffman(ref HuffmanTable table)
{
this.CheckBits();
@ -829,7 +825,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
[MethodImpl(InliningOptions.ColdPath)]
private int DecodeHuffmanSlow(ref HuffmanTable table)
private unsafe int DecodeHuffmanSlow(ref HuffmanTable table)
{
// Naive test is to shift the code_buffer down so k bits are
// valid, then test against MaxCode. To speed this up, we've
@ -839,7 +835,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// that way we don't need to shift inside the loop.
uint temp = this.codeBuffer >> 16;
int k;
for (k = FastBits + 1; ; k++)
for (k = FastBits + 1; ; ++k)
{
if (temp < table.MaxCode[k])
{

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

@ -510,7 +510,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApplicationHeaderMarker(int remaining)
{
if (remaining < 5)
// We can only decode JFif identifiers.
if (remaining < JFifMarker.Length)
{
// Skip the application header length
this.InputStream.Skip(remaining);
@ -746,11 +747,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (!metadataOnly)
{
// No need to pool this. They max out at 4
this.Frame.ComponentIds = new byte[this.Frame.ComponentCount];
this.Frame.Components = new JpegComponent[this.Frame.ComponentCount];
this.Frame.ComponentIds = new byte[this.ComponentCount];
this.Frame.ComponentOrder = new byte[this.ComponentCount];
this.Frame.Components = new JpegComponent[this.ComponentCount];
this.ColorSpace = this.DeduceJpegColorSpace();
for (int i = 0; i < this.Frame.ComponentCount; i++)
for (int i = 0; i < this.ComponentCount; i++)
{
byte hv = this.temp[index + 1];
int h = hv >> 4;
@ -822,10 +824,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
codeLengths.GetSpan(),
huffmanValues.GetSpan());
if (huffmanTableSpec >> 4 != 0)
if (tableType != 0)
{
// Build a table that decodes both magnitude and value of small ACs in one go.
this.fastACTables.BuildACTableLut(huffmanTableSpec & 15, this.acHuffmanTables);
this.fastACTables.BuildACTableLut(tableIndex, this.acHuffmanTables);
}
}
}
@ -866,6 +868,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (selector == id)
{
componentIndex = j;
break;
}
}
@ -878,6 +881,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int tableSpec = this.InputStream.ReadByte();
component.DCHuffmanTableId = tableSpec >> 4;
component.ACHuffmanTableId = tableSpec & 15;
this.Frame.ComponentOrder[i] = (byte)componentIndex;
}
this.InputStream.Read(this.temp, 0, 3);
@ -892,7 +896,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.dcHuffmanTables,
this.acHuffmanTables,
this.fastACTables,
componentIndex,
selectorsCount,
this.resetInterval,
spectralStart,

107
src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs

@ -0,0 +1,107 @@
using System;
using System.Buffers.Binary;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.MetaData;
namespace SixLabors.ImageSharp.Formats.Png.Chunks
{
/// <summary>
/// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image.
/// </summary>
internal readonly struct PhysicalChunkData
{
public const int Size = 9;
public PhysicalChunkData(uint x, uint y, byte unitSpecifier)
{
this.XAxisPixelsPerUnit = x;
this.YAxisPixelsPerUnit = y;
this.UnitSpecifier = unitSpecifier;
}
/// <summary>
/// Gets the number of pixels per unit on the X axis.
/// </summary>
public uint XAxisPixelsPerUnit { get; }
/// <summary>
/// Gets the number of pixels per unit on the Y axis.
/// </summary>
public uint YAxisPixelsPerUnit { get; }
/// <summary>
/// Gets the unit specifier.
/// 0: unit is unknown
/// 1: unit is the meter
/// When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified.
/// </summary>
public byte UnitSpecifier { get; }
/// <summary>
/// Parses the PhysicalChunkData from the given buffer.
/// </summary>
/// <param name="data">The data buffer.</param>
/// <returns>The parsed PhysicalChunkData.</returns>
public static PhysicalChunkData Parse(ReadOnlySpan<byte> data)
{
uint hResolution = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(0, 4));
uint vResolution = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(4, 4));
byte unit = data[8];
return new PhysicalChunkData(hResolution, vResolution, unit);
}
/// <summary>
/// Constructs the PngPhysicalChunkData from the provided metadata.
/// If the resolution units are not in meters, they are automatically convereted.
/// </summary>
/// <param name="meta">The metadata.</param>
/// <returns>The constructed PngPhysicalChunkData instance.</returns>
public static PhysicalChunkData FromMetadata(ImageMetaData meta)
{
byte unitSpecifier = 0;
uint x;
uint y;
switch (meta.ResolutionUnits)
{
case PixelResolutionUnit.AspectRatio:
unitSpecifier = 0; // Unspecified
x = (uint)Math.Round(meta.HorizontalResolution);
y = (uint)Math.Round(meta.VerticalResolution);
break;
case PixelResolutionUnit.PixelsPerInch:
unitSpecifier = 1; // Per meter
x = (uint)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution));
y = (uint)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution));
break;
case PixelResolutionUnit.PixelsPerCentimeter:
unitSpecifier = 1; // Per meter
x = (uint)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution));
y = (uint)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution));
break;
default:
unitSpecifier = 1; // Per meter
x = (uint)Math.Round(meta.HorizontalResolution);
y = (uint)Math.Round(meta.VerticalResolution);
break;
}
return new PhysicalChunkData(x, y, unitSpecifier);
}
/// <summary>
/// Writes the data to the given buffer.
/// </summary>
/// <param name="buffer">The buffer.</param>
public void WriteTo(Span<byte> buffer)
{
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(0, 4), this.XAxisPixelsPerUnit);
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4, 4), this.YAxisPixelsPerUnit);
buffer[8] = this.UnitSpecifier;
}
}
}

12
src/ImageSharp/Formats/Png/PngConstants.cs

@ -41,5 +41,17 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The header bytes as a big endian coded ulong.
/// </summary>
public const ulong HeaderValue = 0x89504E470D0A1A0AUL;
/// <summary>
/// The dictionary of available color types.
/// </summary>
public static readonly Dictionary<PngColorType, byte[]> ColorTypes = new Dictionary<PngColorType, byte[]>()
{
[PngColorType.Grayscale] = new byte[] { 1, 2, 4, 8, 16 },
[PngColorType.Rgb] = new byte[] { 8, 16 },
[PngColorType.Palette] = new byte[] { 1, 2, 4, 8 },
[PngColorType.GrayscaleWithAlpha] = new byte[] { 8, 16 },
[PngColorType.RgbWithAlpha] = new byte[] { 8, 16 }
};
}
}

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

@ -3,13 +3,12 @@
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 System.Text;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Memory;
@ -26,31 +25,9 @@ namespace SixLabors.ImageSharp.Formats.Png
internal sealed class PngDecoderCore
{
/// <summary>
/// The dictionary of available color types.
/// Reusable buffer.
/// </summary>
private static readonly Dictionary<PngColorType, byte[]> ColorTypes = new Dictionary<PngColorType, byte[]>()
{
[PngColorType.Grayscale] = new byte[] { 1, 2, 4, 8, 16 },
[PngColorType.Rgb] = new byte[] { 8, 16 },
[PngColorType.Palette] = new byte[] { 1, 2, 4, 8 },
[PngColorType.GrayscaleWithAlpha] = new byte[] { 8, 16 },
[PngColorType.RgbWithAlpha] = new byte[] { 8, 16 }
};
/// <summary>
/// Reusable buffer for reading chunk types.
/// </summary>
private readonly byte[] chunkTypeBuffer = new byte[4];
/// <summary>
/// Reusable buffer for reading chunk lengths.
/// </summary>
private readonly byte[] chunkLengthBuffer = new byte[4];
/// <summary>
/// Reusable buffer for reading crc values.
/// </summary>
private readonly byte[] crcBuffer = new byte[4];
private readonly byte[] buffer = new byte[4];
/// <summary>
/// Reusable crc for validating chunks.
@ -220,7 +197,6 @@ namespace SixLabors.ImageSharp.Formats.Png
{
case PngChunkType.Header:
this.ReadHeaderChunk(pngMetaData, chunk.Data.Array);
this.ValidateHeader();
break;
case PngChunkType.Physical:
this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan());
@ -253,7 +229,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.AssignTransparentMarkers(alpha);
break;
case PngChunkType.Text:
this.ReadTextChunk(metaData, chunk.Data.Array, chunk.Length);
this.ReadTextChunk(metaData, chunk.Data.Array.AsSpan(0, chunk.Length));
break;
case PngChunkType.Exif:
if (!this.ignoreMetadata)
@ -309,7 +285,6 @@ namespace SixLabors.ImageSharp.Formats.Png
{
case PngChunkType.Header:
this.ReadHeaderChunk(pngMetaData, chunk.Data.Array);
this.ValidateHeader();
break;
case PngChunkType.Physical:
this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan());
@ -321,7 +296,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.SkipChunkDataAndCrc(chunk);
break;
case PngChunkType.Text:
this.ReadTextChunk(metaData, chunk.Data.Array, chunk.Length);
this.ReadTextChunk(metaData, chunk.Data.Array.AsSpan(0, chunk.Length));
break;
case PngChunkType.End:
this.isEndChunkReached = true;
@ -402,26 +377,14 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="data">The data containing physical data.</param>
private void ReadPhysicalChunk(ImageMetaData metadata, ReadOnlySpan<byte> data)
{
// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. It contains:
// Pixels per unit, X axis: 4 bytes (unsigned integer)
// Pixels per unit, Y axis: 4 bytes (unsigned integer)
// Unit specifier: 1 byte
//
// The following values are legal for the unit specifier:
// 0: unit is unknown
// 1: unit is the meter
//
// When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified.
int hResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4));
int vResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4));
byte unit = data[8];
metadata.ResolutionUnits = unit == byte.MinValue
var physicalChunk = PhysicalChunkData.Parse(data);
metadata.ResolutionUnits = physicalChunk.UnitSpecifier == byte.MinValue
? PixelResolutionUnit.AspectRatio
: PixelResolutionUnit.PixelsPerMeter;
metadata.HorizontalResolution = hResolution;
metadata.VerticalResolution = vResolution;
metadata.HorizontalResolution = physicalChunk.XAxisPixelsPerUnit;
metadata.VerticalResolution = physicalChunk.YAxisPixelsPerUnit;
}
/// <summary>
@ -573,22 +536,18 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case FilterType.Sub:
SubFilter.Decode(scanlineSpan, this.bytesPerPixel);
break;
case FilterType.Up:
UpFilter.Decode(scanlineSpan, this.previousScanline.GetSpan());
break;
case FilterType.Average:
AverageFilter.Decode(scanlineSpan, this.previousScanline.GetSpan(), this.bytesPerPixel);
break;
case FilterType.Paeth:
PaethFilter.Decode(scanlineSpan, this.previousScanline.GetSpan(), this.bytesPerPixel);
break;
@ -647,22 +606,18 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case FilterType.Sub:
SubFilter.Decode(scanSpan, this.bytesPerPixel);
break;
case FilterType.Up:
UpFilter.Decode(scanSpan, prevSpan);
break;
case FilterType.Average:
AverageFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel);
break;
case FilterType.Paeth:
PaethFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel);
break;
@ -715,7 +670,6 @@ namespace SixLabors.ImageSharp.Formats.Png
switch (this.pngColorType)
{
case PngColorType.Grayscale:
PngScanlineProcessor.ProcessGrayscaleScanline(
this.header,
scanlineSpan,
@ -727,7 +681,6 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngColorType.GrayscaleWithAlpha:
PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline(
this.header,
scanlineSpan,
@ -738,7 +691,6 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngColorType.Palette:
PngScanlineProcessor.ProcessPaletteScanline(
this.header,
scanlineSpan,
@ -749,7 +701,6 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngColorType.Rgb:
PngScanlineProcessor.ProcessRgbScanline(
this.header,
scanlineSpan,
@ -763,7 +714,6 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngColorType.RgbWithAlpha:
PngScanlineProcessor.ProcessRgbaScanline(
this.header,
scanlineSpan,
@ -799,7 +749,6 @@ namespace SixLabors.ImageSharp.Formats.Png
switch (this.pngColorType)
{
case PngColorType.Grayscale:
PngScanlineProcessor.ProcessInterlacedGrayscaleScanline(
this.header,
scanlineSpan,
@ -813,7 +762,6 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngColorType.GrayscaleWithAlpha:
PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline(
this.header,
scanlineSpan,
@ -826,7 +774,6 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngColorType.Palette:
PngScanlineProcessor.ProcessInterlacedPaletteScanline(
this.header,
scanlineSpan,
@ -839,7 +786,6 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngColorType.Rgb:
PngScanlineProcessor.ProcessInterlacedRgbScanline(
this.header,
scanlineSpan,
@ -855,7 +801,6 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngColorType.RgbWithAlpha:
PngScanlineProcessor.ProcessInterlacedRgbaScanline(
this.header,
scanlineSpan,
@ -886,6 +831,7 @@ namespace SixLabors.ImageSharp.Formats.Png
ushort rc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(0, 2));
ushort gc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(2, 2));
ushort bc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(4, 2));
this.rgb48Trans = new Rgb48(rc, gc, bc);
this.hasTrans = true;
return;
@ -925,37 +871,10 @@ namespace SixLabors.ImageSharp.Formats.Png
{
this.header = PngHeader.Parse(data);
this.header.Validate();
pngMetaData.BitDepth = (PngBitDepth)this.header.BitDepth;
pngMetaData.ColorType = this.header.ColorType;
}
/// <summary>
/// Validates the png header.
/// </summary>
/// <exception cref="NotSupportedException">
/// Thrown if the image does pass validation.
/// </exception>
private void ValidateHeader()
{
if (!ColorTypes.ContainsKey(this.header.ColorType))
{
throw new NotSupportedException("Color type is not supported or not valid.");
}
if (!ColorTypes[this.header.ColorType].Contains(this.header.BitDepth))
{
throw new NotSupportedException("Bit depth is not supported or not valid.");
}
if (this.header.FilterMethod != 0)
{
throw new NotSupportedException("The png specification only defines 0 as filter method.");
}
if (this.header.InterlaceMethod != PngInterlaceMode.None && this.header.InterlaceMethod != PngInterlaceMode.Adam7)
{
throw new NotSupportedException("The png specification only defines 'None' and 'Adam7' as interlaced methods.");
}
this.pngColorType = this.header.ColorType;
}
@ -964,28 +883,18 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Reads a text chunk containing image properties from the data.
/// </summary>
/// <param name="metadata">The metadata to decode to.</param>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
/// <param name="length">The maximum length to read.</param>
private void ReadTextChunk(ImageMetaData metadata, byte[] data, int length)
/// <param name="data">The <see cref="T:Span"/> containing the data.</param>
private void ReadTextChunk(ImageMetaData metadata, ReadOnlySpan<byte> data)
{
if (this.ignoreMetadata)
{
return;
}
int zeroIndex = 0;
for (int i = 0; i < length; i++)
{
if (data[i] == 0)
{
zeroIndex = i;
break;
}
}
int zeroIndex = data.IndexOf((byte)0);
string name = this.textEncoding.GetString(data, 0, zeroIndex);
string value = this.textEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1);
string name = this.textEncoding.GetString(data.Slice(0, zeroIndex));
string value = this.textEncoding.GetString(data.Slice(zeroIndex + 1));
metadata.Properties.Add(new ImageProperty(name, value));
}
@ -1001,7 +910,7 @@ namespace SixLabors.ImageSharp.Formats.Png
return 0;
}
this.currentStream.Read(this.crcBuffer, 0, 4);
this.currentStream.Read(this.buffer, 0, 4);
if (this.TryReadChunk(out PngChunk chunk))
{
@ -1086,13 +995,17 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="chunk">The <see cref="PngChunk"/>.</param>
private void ValidateChunk(in PngChunk chunk)
{
Span<byte> chunkType = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type);
this.crc.Reset();
this.crc.Update(this.chunkTypeBuffer);
this.crc.Update(chunkType);
this.crc.Update(chunk.Data.GetSpan());
if (this.crc.Value != chunk.Crc)
{
string chunkTypeName = Encoding.UTF8.GetString(this.chunkTypeBuffer, 0, 4);
string chunkTypeName = Encoding.UTF8.GetString(chunkType);
throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
}
@ -1106,14 +1019,9 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </exception>
private uint ReadChunkCrc()
{
int numBytes = this.currentStream.Read(this.crcBuffer, 0, 4);
if (numBytes >= 1 && numBytes <= 3)
{
throw new ImageFormatException("Image stream is not valid!");
}
return BinaryPrimitives.ReadUInt32BigEndian(this.crcBuffer);
return this.currentStream.Read(this.buffer, 0, 4) == 4
? BinaryPrimitives.ReadUInt32BigEndian(this.buffer)
: throw new ImageFormatException("Image stream is not valid!");
}
/// <summary>
@ -1148,22 +1056,22 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </exception>
private PngChunkType ReadChunkType()
{
return this.currentStream.Read(this.chunkTypeBuffer, 0, 4) == 4
? (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.chunkTypeBuffer.AsSpan())
return this.currentStream.Read(this.buffer, 0, 4) == 4
? (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer)
: throw new ImageFormatException("Invalid PNG data.");
}
/// <summary>
/// Calculates the length of the given chunk.
/// Attempts to read the length of the next chunk.
/// </summary>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
/// <returns>
/// Whether the the length was read.
/// </returns>
private bool TryReadChunkLength(out int result)
{
if (this.currentStream.Read(this.chunkLengthBuffer, 0, 4) == 4)
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
{
result = BinaryPrimitives.ReadInt32BigEndian(this.chunkLengthBuffer);
result = BinaryPrimitives.ReadInt32BigEndian(this.buffer);
return true;
}

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

@ -9,7 +9,7 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Memory;
@ -674,47 +674,9 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="meta">The image meta data.</param>
private void WritePhysicalChunk(Stream stream, ImageMetaData meta)
{
// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. It contains:
// Pixels per unit, X axis: 4 bytes (unsigned integer)
// Pixels per unit, Y axis: 4 bytes (unsigned integer)
// Unit specifier: 1 byte
//
// The following values are legal for the unit specifier:
// 0: unit is unknown
// 1: unit is the meter
//
// When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified.
Span<byte> hResolution = this.chunkDataBuffer.AsSpan(0, 4);
Span<byte> vResolution = this.chunkDataBuffer.AsSpan(4, 4);
switch (meta.ResolutionUnits)
{
case PixelResolutionUnit.AspectRatio:
this.chunkDataBuffer[8] = 0;
BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution));
BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.VerticalResolution));
break;
case PixelResolutionUnit.PixelsPerInch:
this.chunkDataBuffer[8] = 1; // Per meter
BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution)));
BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution)));
break;
case PixelResolutionUnit.PixelsPerCentimeter:
this.chunkDataBuffer[8] = 1; // Per meter
BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution)));
BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution)));
break;
default:
this.chunkDataBuffer[8] = 1; // Per meter
BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution));
BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.VerticalResolution));
break;
}
PhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer);
this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9);
this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, PhysicalChunkData.Size);
}
/// <summary>

30
src/ImageSharp/Formats/Png/PngHeader.cs

@ -80,6 +80,36 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
public PngInterlaceMode InterlaceMethod { get; }
/// <summary>
/// Validates the png header.
/// </summary>
/// <exception cref="NotSupportedException">
/// Thrown if the image does pass validation.
/// </exception>
public void Validate()
{
if (!PngConstants.ColorTypes.TryGetValue(this.ColorType, out byte[] supportedBitDepths))
{
throw new NotSupportedException($"Invalid or unsupported color type. Was '{this.ColorType}'.");
}
if (supportedBitDepths.AsSpan().IndexOf(this.BitDepth) == -1)
{
throw new NotSupportedException($"Invalid or unsupported bit depth. Was '{this.BitDepth}'.");
}
if (this.FilterMethod != 0)
{
throw new NotSupportedException($"Invalid filter method. Expected 0. Was '{this.FilterMethod}'.");
}
// The png specification only defines 'None' and 'Adam7' as interlaced methods.
if (this.InterlaceMethod != PngInterlaceMode.None && this.InterlaceMethod != PngInterlaceMode.Adam7)
{
throw new NotSupportedException($"Invalid interlace method. Expected 'None' or 'Adam7'. Was '{this.InterlaceMethod}'.");
}
}
/// <summary>
/// Writes the header to the given buffer.
/// </summary>

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

@ -127,25 +127,14 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
private unsafe string ConvertToString(ReadOnlySpan<byte> buffer)
{
Span<byte> nullChar = stackalloc byte[1] { 0 };
int nullCharIndex = buffer.IndexOf(nullChar);
int nullCharIndex = buffer.IndexOf((byte)0);
if (nullCharIndex > -1)
{
buffer = buffer.Slice(0, nullCharIndex);
}
#if NETSTANDARD1_1
return Encoding.UTF8.GetString(buffer.ToArray(), 0, buffer.Length);
#elif NETCOREAPP2_1
return Encoding.UTF8.GetString(buffer);
#else
fixed (byte* pointer = &MemoryMarshal.GetReference(buffer))
{
return Encoding.UTF8.GetString(pointer, buffer.Length);
}
#endif
}
/// <summary>

50
src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.cs

@ -24,13 +24,14 @@ namespace SixLabors.ImageSharp.PixelFormats
ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(source);
ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels);
var rgba = new Rgba64(0, 0, 0, 65535);
// For conversion methods writing only to RGB channels, we need to keep the alpha channel opaque!
var temp = NamedColors<Rgba64>.Black;
for (int i = 0; i < count; i++)
{
ref TPixel dp = ref Unsafe.Add(ref destRef, i);
rgba = Unsafe.Add(ref sourceRef, i);
dp.PackFromRgba64(rgba);
temp = Unsafe.Add(ref sourceRef, i);
dp.PackFromRgba64(temp);
}
}
@ -95,13 +96,14 @@ namespace SixLabors.ImageSharp.PixelFormats
ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(source);
ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels);
var rgb = default(Rgb48);
// For conversion methods writing only to RGB channels, we need to keep the alpha channel opaque!
var temp = NamedColors<Rgb48>.Black;
for (int i = 0; i < count; i++)
{
ref TPixel dp = ref Unsafe.Add(ref destRef, i);
rgb = Unsafe.Add(ref sourceRef, i);
dp.PackFromRgb48(rgb);
temp = Unsafe.Add(ref sourceRef, i);
dp.PackFromRgb48(temp);
}
}
@ -166,13 +168,14 @@ namespace SixLabors.ImageSharp.PixelFormats
ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(source);
ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels);
var rgba = new Rgba32(0, 0, 0, 255);
// For conversion methods writing only to RGB channels, we need to keep the alpha channel opaque!
var temp = NamedColors<Rgba32>.Black;
for (int i = 0; i < count; i++)
{
ref TPixel dp = ref Unsafe.Add(ref destRef, i);
rgba = Unsafe.Add(ref sourceRef, i);
dp.PackFromRgba32(rgba);
temp = Unsafe.Add(ref sourceRef, i);
dp.PackFromRgba32(temp);
}
}
@ -237,13 +240,14 @@ namespace SixLabors.ImageSharp.PixelFormats
ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(source);
ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels);
var bgra = new Bgra32(0, 0, 0, 255);
// For conversion methods writing only to RGB channels, we need to keep the alpha channel opaque!
var temp = NamedColors<Bgra32>.Black;
for (int i = 0; i < count; i++)
{
ref TPixel dp = ref Unsafe.Add(ref destRef, i);
bgra = Unsafe.Add(ref sourceRef, i);
dp.PackFromBgra32(bgra);
temp = Unsafe.Add(ref sourceRef, i);
dp.PackFromBgra32(temp);
}
}
@ -308,13 +312,14 @@ namespace SixLabors.ImageSharp.PixelFormats
ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(source);
ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels);
var rgba = new Rgba32(0, 0, 0, 255);
// For conversion methods writing only to RGB channels, we need to keep the alpha channel opaque!
var temp = NamedColors<Rgba32>.Black;
for (int i = 0; i < count; i++)
{
ref TPixel dp = ref Unsafe.Add(ref destRef, i);
rgba.Rgb = Unsafe.Add(ref sourceRef, i);
dp.PackFromRgba32(rgba);
temp.Rgb = Unsafe.Add(ref sourceRef, i);
dp.PackFromRgba32(temp);
}
}
@ -379,13 +384,14 @@ namespace SixLabors.ImageSharp.PixelFormats
ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(source);
ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels);
var rgba = new Rgba32(0, 0, 0, 255);
// For conversion methods writing only to RGB channels, we need to keep the alpha channel opaque!
var temp = NamedColors<Rgba32>.Black;
for (int i = 0; i < count; i++)
{
ref TPixel dp = ref Unsafe.Add(ref destRef, i);
rgba.Bgr = Unsafe.Add(ref sourceRef, i);
dp.PackFromRgba32(rgba);
temp.Bgr = Unsafe.Add(ref sourceRef, i);
dp.PackFromRgba32(temp);
}
}
@ -450,13 +456,14 @@ namespace SixLabors.ImageSharp.PixelFormats
ref Argb32 sourceRef = ref MemoryMarshal.GetReference(source);
ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels);
var argb = new Argb32(0, 0, 0, 255);
// For conversion methods writing only to RGB channels, we need to keep the alpha channel opaque!
var temp = NamedColors<Argb32>.Black;
for (int i = 0; i < count; i++)
{
ref TPixel dp = ref Unsafe.Add(ref destRef, i);
argb = Unsafe.Add(ref sourceRef, i);
dp.PackFromArgb32(argb);
temp = Unsafe.Add(ref sourceRef, i);
dp.PackFromArgb32(temp);
}
}
@ -509,4 +516,5 @@ namespace SixLabors.ImageSharp.PixelFormats
}
}
}

235
src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.tt

@ -10,132 +10,8 @@
<#@ import namespace="System.Runtime.InteropServices" #>
<#@ output extension=".cs" #>
<#
void GenerateToDestFormatMethods(string pixelType)
{
#>
/// <summary>
/// Converts 'count' pixels in 'sourcePixels` span to a span of <see cref="<#=pixelType#>"/>-s.
/// Bulk version of <see cref="IPixel.To<#=pixelType#>(ref <#=pixelType#>)"/>.
/// </summary>
/// <param name="sourcePixels">The span of source pixels</param>
/// <param name="dest">The destination span of <see cref="<#=pixelType#>"/> data.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void To<#=pixelType#>(ReadOnlySpan<TPixel> sourcePixels, Span<<#=pixelType#>> dest, int count)
{
GuardSpans(sourcePixels, nameof(sourcePixels), dest, nameof(dest), count);
ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels);
ref <#=pixelType#> destBaseRef = ref MemoryMarshal.GetReference(dest);
for (int i = 0; i < count; i++)
{
ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i);
ref <#=pixelType#> dp = ref Unsafe.Add(ref destBaseRef, i);
sp.To<#=pixelType#>(ref dp);
}
}
/// <summary>
/// A helper for <see cref="To<#=pixelType#>(ReadOnlySpan{TPixel}, Span{<#=pixelType#>}, int)"/> that expects a byte span as destination.
/// The layout of the data in 'destBytes' must be compatible with <see cref="<#=pixelType#>"/> layout.
/// </summary>
/// <param name="sourceColors">The <see cref="Span{T}"/> to the source colors.</param>
/// <param name="destBytes">The <see cref="Span{T}"/> to the destination bytes.</param>
/// <param name="count">The number of pixels to convert.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void To<#=pixelType#>Bytes(ReadOnlySpan<TPixel> sourceColors, Span<byte> destBytes, int count)
{
this.To<#=pixelType#>(sourceColors, MemoryMarshal.Cast<byte, <#=pixelType#>>(destBytes), count);
}
<#
}
void GeneratePackFromMethodUsingPackFromRgba64(string pixelType, string rgbaOperationCode)
{
#>
/// <summary>
/// Converts 'count' elements in 'source` span of <see cref="<#=pixelType#>"/> data to a span of <typeparamref name="TPixel"/>-s.
/// </summary>
/// <param name="source">The source <see cref="Span{T}"/> of <see cref="<#=pixelType#>"/> data.</param>
/// <param name="destPixels">The <see cref="Span{T}"/> to the destination pixels.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void PackFrom<#=pixelType#>(ReadOnlySpan<<#=pixelType#>> source, Span<TPixel> destPixels, int count)
{
GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count);
ref <#=pixelType#> sourceRef = ref MemoryMarshal.GetReference(source);
ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels);
var rgba = new Rgba64(0, 0, 0, 65535);
for (int i = 0; i < count; i++)
{
ref TPixel dp = ref Unsafe.Add(ref destRef, i);
<#=rgbaOperationCode#>
dp.PackFromRgba64(rgba);
}
}
/// <summary>
/// A helper for <see cref="PackFrom<#=pixelType#>(ReadOnlySpan{<#=pixelType#>}, Span{TPixel}, int)"/> that expects a byte span.
/// The layout of the data in 'sourceBytes' must be compatible with <see cref="<#=pixelType#>"/> layout.
/// </summary>
/// <param name="sourceBytes">The <see cref="ReadOnlySpan{T}"/> to the source bytes.</param>
/// <param name="destPixels">The <see cref="Span{T}"/> to the destination pixels.</param>
/// <param name="count">The number of pixels to convert.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void PackFrom<#=pixelType#>Bytes(ReadOnlySpan<byte> sourceBytes, Span<TPixel> destPixels, int count)
{
this.PackFrom<#=pixelType#>(MemoryMarshal.Cast<byte, <#=pixelType#>>(sourceBytes), destPixels, count);
}
<#
}
void GeneratePackFromMethodUsingPackFromRgb48(string pixelType, string rgbaOperationCode)
{
#>
/// <summary>
/// Converts 'count' elements in 'source` span of <see cref="<#=pixelType#>"/> data to a span of <typeparamref name="TPixel"/>-s.
/// </summary>
/// <param name="source">The source <see cref="Span{T}"/> of <see cref="<#=pixelType#>"/> data.</param>
/// <param name="destPixels">The <see cref="Span{T}"/> to the destination pixels.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void PackFrom<#=pixelType#>(ReadOnlySpan<<#=pixelType#>> source, Span<TPixel> destPixels, int count)
{
GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count);
ref <#=pixelType#> sourceRef = ref MemoryMarshal.GetReference(source);
ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels);
var rgb = default(Rgb48);
for (int i = 0; i < count; i++)
{
ref TPixel dp = ref Unsafe.Add(ref destRef, i);
<#=rgbaOperationCode#>
dp.PackFromRgb48(rgb);
}
}
/// <summary>
/// A helper for <see cref="PackFrom<#=pixelType#>(ReadOnlySpan{<#=pixelType#>}, Span{TPixel}, int)"/> that expects a byte span.
/// The layout of the data in 'sourceBytes' must be compatible with <see cref="<#=pixelType#>"/> layout.
/// </summary>
/// <param name="sourceBytes">The <see cref="ReadOnlySpan{T}"/> to the source bytes.</param>
/// <param name="destPixels">The <see cref="Span{T}"/> to the destination pixels.</param>
/// <param name="count">The number of pixels to convert.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void PackFrom<#=pixelType#>Bytes(ReadOnlySpan<byte> sourceBytes, Span<TPixel> destPixels, int count)
{
this.PackFrom<#=pixelType#>(MemoryMarshal.Cast<byte, <#=pixelType#>>(sourceBytes), destPixels, count);
}
<#
}
void GeneratePackFromMethodUsingPackFromRgba32(string pixelType, string rgbaOperationCode)
void GeneratePackFromMethods(string pixelType, string tempPixelType, string assignToTempCode)
{
#>
@ -152,13 +28,14 @@
ref <#=pixelType#> sourceRef = ref MemoryMarshal.GetReference(source);
ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels);
var rgba = new Rgba32(0, 0, 0, 255);
// For conversion methods writing only to RGB channels, we need to keep the alpha channel opaque!
var temp = NamedColors<<#=tempPixelType#>>.Black;
for (int i = 0; i < count; i++)
{
ref TPixel dp = ref Unsafe.Add(ref destRef, i);
<#=rgbaOperationCode#>
dp.PackFromRgba32(rgba);
<#=assignToTempCode#>
dp.PackFrom<#=tempPixelType#>(temp);
}
}
@ -177,86 +54,43 @@
<#
}
void GeneratePackFromMethodUsingPackFromArgb32(string pixelType, string argbOperationCode)
{
#>
void GenerateToDestFormatMethods(string pixelType)
{
#>
/// <summary>
/// Converts 'count' elements in 'source` span of <see cref="<#=pixelType#>"/> data to a span of <typeparamref name="TPixel"/>-s.
/// Converts 'count' pixels in 'sourcePixels` span to a span of <see cref="<#=pixelType#>"/>-s.
/// Bulk version of <see cref="IPixel.To<#=pixelType#>(ref <#=pixelType#>)"/>.
/// </summary>
/// <param name="source">The source <see cref="Span{T}"/> of <see cref="<#=pixelType#>"/> data.</param>
/// <param name="destPixels">The <see cref="Span{T}"/> to the destination pixels.</param>
/// <param name="sourcePixels">The span of source pixels</param>
/// <param name="dest">The destination span of <see cref="<#=pixelType#>"/> data.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void PackFrom<#=pixelType#>(ReadOnlySpan<<#=pixelType#>> source, Span<TPixel> destPixels, int count)
internal virtual void To<#=pixelType#>(ReadOnlySpan<TPixel> sourcePixels, Span<<#=pixelType#>> dest, int count)
{
GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count);
ref <#=pixelType#> sourceRef = ref MemoryMarshal.GetReference(source);
ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels);
GuardSpans(sourcePixels, nameof(sourcePixels), dest, nameof(dest), count);
var argb = new Argb32(0, 0, 0, 255);
ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels);
ref <#=pixelType#> destBaseRef = ref MemoryMarshal.GetReference(dest);
for (int i = 0; i < count; i++)
{
ref TPixel dp = ref Unsafe.Add(ref destRef, i);
<#=argbOperationCode#>
dp.PackFromArgb32(argb);
ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i);
ref <#=pixelType#> dp = ref Unsafe.Add(ref destBaseRef, i);
sp.To<#=pixelType#>(ref dp);
}
}
/// <summary>
/// A helper for <see cref="PackFrom<#=pixelType#>(ReadOnlySpan{<#=pixelType#>}, Span{TPixel}, int)"/> that expects a byte span.
/// The layout of the data in 'sourceBytes' must be compatible with <see cref="<#=pixelType#>"/> layout.
/// </summary>
/// <param name="sourceBytes">The <see cref="ReadOnlySpan{T}"/> to the source bytes.</param>
/// <param name="destPixels">The <see cref="Span{T}"/> to the destination pixels.</param>
/// <param name="count">The number of pixels to convert.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void PackFrom<#=pixelType#>Bytes(ReadOnlySpan<byte> sourceBytes, Span<TPixel> destPixels, int count)
{
this.PackFrom<#=pixelType#>(MemoryMarshal.Cast<byte, <#=pixelType#>>(sourceBytes), destPixels, count);
}
<#
}
void GeneratePackFromMethodUsingPackFromBgra32(string pixelType, string bgraOperationCode)
{
#>
/// <summary>
/// Converts 'count' elements in 'source` span of <see cref="<#=pixelType#>"/> data to a span of <typeparamref name="TPixel"/>-s.
/// </summary>
/// <param name="source">The source <see cref="Span{T}"/> of <see cref="<#=pixelType#>"/> data.</param>
/// <param name="destPixels">The <see cref="Span{T}"/> to the destination pixels.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void PackFrom<#=pixelType#>(ReadOnlySpan<<#=pixelType#>> source, Span<TPixel> destPixels, int count)
{
GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count);
ref <#=pixelType#> sourceRef = ref MemoryMarshal.GetReference(source);
ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels);
var bgra = new Bgra32(0, 0, 0, 255);
for (int i = 0; i < count; i++)
{
ref TPixel dp = ref Unsafe.Add(ref destRef, i);
<#=bgraOperationCode#>
dp.PackFromBgra32(bgra);
}
}
/// <summary>
/// A helper for <see cref="PackFrom<#=pixelType#>(ReadOnlySpan{<#=pixelType#>}, Span{TPixel}, int)"/> that expects a byte span.
/// The layout of the data in 'sourceBytes' must be compatible with <see cref="<#=pixelType#>"/> layout.
/// A helper for <see cref="To<#=pixelType#>(ReadOnlySpan{TPixel}, Span{<#=pixelType#>}, int)"/> that expects a byte span as destination.
/// The layout of the data in 'destBytes' must be compatible with <see cref="<#=pixelType#>"/> layout.
/// </summary>
/// <param name="sourceBytes">The <see cref="ReadOnlySpan{T}"/> to the source bytes.</param>
/// <param name="destPixels">The <see cref="Span{T}"/> to the destination pixels.</param>
/// <param name="sourceColors">The <see cref="Span{T}"/> to the source colors.</param>
/// <param name="destBytes">The <see cref="Span{T}"/> to the destination bytes.</param>
/// <param name="count">The number of pixels to convert.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void PackFrom<#=pixelType#>Bytes(ReadOnlySpan<byte> sourceBytes, Span<TPixel> destPixels, int count)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void To<#=pixelType#>Bytes(ReadOnlySpan<TPixel> sourceColors, Span<byte> destBytes, int count)
{
this.PackFrom<#=pixelType#>(MemoryMarshal.Cast<byte, <#=pixelType#>>(sourceBytes), destPixels, count);
this.To<#=pixelType#>(sourceColors, MemoryMarshal.Cast<byte, <#=pixelType#>>(destBytes), count);
}
<#
}
@ -275,29 +109,28 @@ namespace SixLabors.ImageSharp.PixelFormats
public partial class PixelOperations<TPixel>
{
<#
GeneratePackFromMethodUsingPackFromRgba64("Rgba64", "rgba = Unsafe.Add(ref sourceRef, i);");
GeneratePackFromMethods("Rgba64", "Rgba64", "temp = Unsafe.Add(ref sourceRef, i);");
GenerateToDestFormatMethods("Rgba64");
GeneratePackFromMethodUsingPackFromRgb48("Rgb48", "rgb = Unsafe.Add(ref sourceRef, i);");
GeneratePackFromMethods("Rgb48", "Rgb48", "temp = Unsafe.Add(ref sourceRef, i);");
GenerateToDestFormatMethods("Rgb48");
GeneratePackFromMethodUsingPackFromRgba32("Rgba32", "rgba = Unsafe.Add(ref sourceRef, i);");
GeneratePackFromMethods("Rgba32", "Rgba32", "temp = Unsafe.Add(ref sourceRef, i);");
GenerateToDestFormatMethods("Rgba32");
GeneratePackFromMethodUsingPackFromBgra32("Bgra32", "bgra = Unsafe.Add(ref sourceRef, i);");
GeneratePackFromMethods("Bgra32", "Bgra32", "temp = Unsafe.Add(ref sourceRef, i);");
GenerateToDestFormatMethods("Bgra32");
GeneratePackFromMethodUsingPackFromRgba32("Rgb24", "rgba.Rgb = Unsafe.Add(ref sourceRef, i);");
GeneratePackFromMethods("Rgb24", "Rgba32", "temp.Rgb = Unsafe.Add(ref sourceRef, i);");
GenerateToDestFormatMethods("Rgb24");
GeneratePackFromMethodUsingPackFromRgba32("Bgr24", "rgba.Bgr = Unsafe.Add(ref sourceRef, i);");
GeneratePackFromMethods("Bgr24", "Rgba32", "temp.Bgr = Unsafe.Add(ref sourceRef, i);");
GenerateToDestFormatMethods("Bgr24");
GeneratePackFromMethodUsingPackFromArgb32("Argb32", "argb = Unsafe.Add(ref sourceRef, i);");
GeneratePackFromMethods("Argb32", "Argb32", "temp = Unsafe.Add(ref sourceRef, i);");
GenerateToDestFormatMethods("Argb32");
#>
}
}

2
src/ImageSharp/Processing/CropExtensions.cs

@ -35,6 +35,6 @@ namespace SixLabors.ImageSharp.Processing
/// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Crop<TPixel>(this IImageProcessingContext<TPixel> source, Rectangle cropRectangle)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new CropProcessor<TPixel>(cropRectangle));
=> source.ApplyProcessor(new CropProcessor<TPixel>(cropRectangle, source.GetCurrentSize()));
}
}

7
src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs

@ -22,8 +22,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Initializes a new instance of the <see cref="CropProcessor{TPixel}"/> class.
/// </summary>
/// <param name="cropRectangle">The target cropped rectangle.</param>
public CropProcessor(Rectangle cropRectangle)
/// <param name="sourceSize">The source image size.</param>
public CropProcessor(Rectangle cropRectangle, Size sourceSize)
{
// Check bounds here and throw if we are passed a rectangle exceeding our source bounds.
Guard.IsTrue(new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle), nameof(cropRectangle), "Crop rectangle should be smaller than the source bounds.");
this.CropRectangle = cropRectangle;
}
@ -53,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return;
}
var rect = Rectangle.Intersect(this.CropRectangle, sourceRectangle);
Rectangle rect = this.CropRectangle;
// Copying is cheap, we should process more pixels per task:
ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4);

2
src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs

@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0);
}
new CropProcessor<TPixel>(rectangle).Apply(source, sourceRectangle);
new CropProcessor<TPixel>(rectangle, source.Size()).Apply(source, sourceRectangle);
}
/// <inheritdoc/>

8
tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs

@ -14,7 +14,9 @@ namespace SixLabors.ImageSharp.Tests
private readonly FakeImageOperationsProvider.FakeImageOperations<Rgba32> internalOperations;
protected readonly Rectangle rect;
protected readonly GraphicsOptions options;
private Image<Rgba32> source;
private readonly Image<Rgba32> source;
public Rectangle SourceBounds() => this.source.Bounds();
public BaseImageOperationsExtensionTest()
{
@ -29,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests
{
Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1);
var operation = this.internalOperations.Applied[index];
FakeImageOperationsProvider.FakeImageOperations<Rgba32>.AppliedOperation operation = this.internalOperations.Applied[index];
return Assert.IsType<T>(operation.Processor);
}
@ -38,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests
{
Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1);
var operation = this.internalOperations.Applied[index];
FakeImageOperationsProvider.FakeImageOperations<Rgba32>.AppliedOperation operation = this.internalOperations.Applied[index];
Assert.Equal(rect, operation.Rectangle);
return Assert.IsType<T>(operation.Processor);

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

@ -25,7 +25,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Issues.MultiHuffmanBaseline394,
TestImages.Jpeg.Issues.ExifDecodeOutOfRange694,
TestImages.Jpeg.Issues.InvalidEOI695,
TestImages.Jpeg.Issues.ExifResizeOutOfRange696
TestImages.Jpeg.Issues.ExifResizeOutOfRange696,
TestImages.Jpeg.Issues.InvalidAPP0721
};
public static string[] ProgressiveTestJpegs =
@ -43,6 +44,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Issues.BadRstProgressive518,
TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159,
TestImages.Jpeg.Issues.DhtHasWrongLength624,
TestImages.Jpeg.Issues.OrderedInterleavedProgressive723A,
TestImages.Jpeg.Issues.OrderedInterleavedProgressive723B,
TestImages.Jpeg.Issues.OrderedInterleavedProgressive723C
};
/// <summary>
@ -54,7 +58,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159
};
private static readonly Dictionary<string, float> CustomToleranceValues =
private static readonly Dictionary<string, float> CustomToleranceValues =
new Dictionary<string, float>
{
// Baseline:

13
tests/ImageSharp.Tests/ImageOperationTests.cs

@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void CloneCallsImageOperationsProvider_Func_WithDuplicateImage()
{
var returned = this.image.Clone(x => x.ApplyProcessor(this.processor));
Image<Rgba32> returned = this.image.Clone(x => x.ApplyProcessor(this.processor));
Assert.True(this.provider.HasCreated(returned));
Assert.Contains(this.processor, this.provider.AppliedOperations(returned).Select(x => x.Processor));
@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void CloneCallsImageOperationsProvider_ListOfProcessors_WithDuplicateImage()
{
var returned = this.image.Clone(this.processor);
Image<Rgba32> returned = this.image.Clone(this.processor);
Assert.True(this.provider.HasCreated(returned));
Assert.Contains(this.processor, this.provider.AppliedOperations(returned).Select(x => x.Processor));
@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void CloneCallsImageOperationsProvider_Func_NotOnOrigional()
{
var returned = this.image.Clone(x => x.ApplyProcessor(this.processor));
Image<Rgba32> returned = this.image.Clone(x => x.ApplyProcessor(this.processor));
Assert.False(this.provider.HasCreated(this.image));
Assert.DoesNotContain(this.processor, this.provider.AppliedOperations(this.image).Select(x => x.Processor));
}
@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void CloneCallsImageOperationsProvider_ListOfProcessors_NotOnOrigional()
{
var returned = this.image.Clone(this.processor);
Image<Rgba32> returned = this.image.Clone(this.processor);
Assert.False(this.provider.HasCreated(this.image));
Assert.DoesNotContain(this.processor, this.provider.AppliedOperations(this.image).Select(x => x.Processor));
}
@ -95,9 +95,6 @@ namespace SixLabors.ImageSharp.Tests
Assert.Contains(this.processor, operations.Applied.Select(x => x.Processor));
}
public void Dispose()
{
this.image.Dispose();
}
public void Dispose() => this.image.Dispose();
}
}

1
tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs

@ -17,7 +17,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
[Theory]
[WithTestPatternImages(70, 30, PixelTypes.Rgba32, 0, 0, 70, 30)]
[WithTestPatternImages(50, 50, PixelTypes.Rgba32, -1, -1, 100, 200)]
[WithTestPatternImages(30, 70, PixelTypes.Rgba32, 7, 13, 20, 50)]
public void Crop<TPixel>(TestImageProvider<TPixel> provider, int x, int y, int w, int h)
where TPixel : struct, IPixel<TPixel>

8
tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
@ -33,5 +34,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
Assert.Equal(cropRectangle, processor.CropRectangle);
}
[Fact]
public void CropRectangleWithInvalidBoundsThrowsException()
{
var cropRectangle = Rectangle.Inflate(this.SourceBounds(), 5, 5);
Assert.Throws<ArgumentException>(() => this.operations.Crop(cropRectangle));
}
}
}

4
tests/ImageSharp.Tests/TestImages.cs

@ -155,6 +155,10 @@ namespace SixLabors.ImageSharp.Tests
public const string ExifDecodeOutOfRange694 = "Jpg/issues/Issue694-Decode-Exif-OutOfRange.jpg";
public const string InvalidEOI695 = "Jpg/issues/Issue695-Invalid-EOI.jpg";
public const string ExifResizeOutOfRange696 = "Jpg/issues/Issue696-Resize-Exif-OutOfRange.jpg";
public const string InvalidAPP0721 = "Jpg/issues/Issue721-InvalidAPP0.jpg";
public const string OrderedInterleavedProgressive723A = "Jpg/issues/Issue723-Ordered-Interleaved-Progressive-A.jpg";
public const string OrderedInterleavedProgressive723B = "Jpg/issues/Issue723-Ordered-Interleaved-Progressive-B.jpg";
public const string OrderedInterleavedProgressive723C = "Jpg/issues/Issue723-Ordered-Interleaved-Progressive-C.jpg";
}
public static readonly string[] All = Baseline.All.Concat(Progressive.All).ToArray();

2
tests/Images/External

@ -1 +1 @@
Subproject commit 7f4d2d64c6b820ca2b6827e6a8540a1013305ccf
Subproject commit c0627f384c1d3d2f8d914c9578ae31354c35fd2c

BIN
tests/Images/Input/Jpg/issues/Issue721-InvalidAPP0.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-A.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-B.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-C.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Loading…
Cancel
Save