Browse Source

Merge branch 'foo' into without46

af/merge-core
Anton Firszov 10 years ago
parent
commit
dea89ee374
  1. 2
      src/ImageSharp/Common/Extensions/ByteExtensions.cs
  2. 13
      src/ImageSharp/Common/Extensions/ComparableExtensions.cs
  3. 15
      src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
  4. 7
      src/ImageSharp/Formats/Png/Filters/NoneFilter.cs
  5. 13
      src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
  6. 13
      src/ImageSharp/Formats/Png/Filters/SubFilter.cs
  7. 13
      src/ImageSharp/Formats/Png/Filters/UpFilter.cs
  8. 101
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  9. 102
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  10. 10
      src/ImageSharp/IO/EndianBitConverter.cs
  11. 20
      src/ImageSharp/Numerics/Rectangle.cs
  12. 82
      src/ImageSharp/Profiles/Exif/ExifReader.cs
  13. 178
      src/ImageSharp/Profiles/Exif/ExifValue.cs
  14. 18
      src/ImageSharp/Profiles/Exif/ExifWriter.cs
  15. 34
      src/ImageSharp/Samplers/Processors/RotateProcessor.cs
  16. 19
      tests/ImageSharp.Tests/Formats/Png/PngTests.cs

2
src/ImageSharp/Common/Extensions/ByteExtensions.cs

@ -24,7 +24,7 @@ namespace ImageSharp
public static byte[] ToArrayByBitsLength(this byte[] source, int bits)
{
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThan(bits, 0, "bits");
Guard.MustBeGreaterThan(bits, 0, nameof(bits));
byte[] result;

13
src/ImageSharp/Common/Extensions/ComparableExtensions.cs

@ -165,5 +165,18 @@ namespace ImageSharp
{
return (byte)value.Clamp(0, 255);
}
/// <summary>
/// Swaps the references to two objects in memory.
/// </summary>
/// <param name="first">The first reference.</param>
/// <param name="second">The second reference.</param>
/// <typeparam name="T">The type of object.</typeparam>
public static void Swap<T>(ref T first, ref T second)
{
T temp = second;
second = first;
first = temp;
}
}
}

15
src/ImageSharp/Formats/Png/Filters/AverageFilter.cs

@ -20,13 +20,9 @@ namespace ImageSharp.Formats
/// <param name="scanline">The scanline to decode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <returns>
/// The <see cref="T:byte[]"/>
/// </returns>
public static byte[] Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
{
// Average(x) + floor((Raw(x-bpp)+Prior(x))/2)
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
{
@ -38,8 +34,6 @@ namespace ImageSharp.Formats
scan[x] = (byte)((scan[x] + Average(left, above)) % 256);
}
}
return scanline;
}
/// <summary>
@ -47,13 +41,12 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel, int bytesPerScanline)
public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel, int bytesPerScanline)
{
// Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2)
byte[] result = new byte[bytesPerScanline + 1];
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
fixed (byte* res = result)
@ -68,8 +61,6 @@ namespace ImageSharp.Formats
res[x + 1] = (byte)((scan[x] - Average(left, above)) % 256);
}
}
return result;
}
/// <summary>

7
src/ImageSharp/Formats/Png/Filters/NoneFilter.cs

@ -29,14 +29,13 @@ namespace ImageSharp.Formats
/// Encodes the scanline
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Encode(byte[] scanline, int bytesPerScanline)
public static void Encode(byte[] scanline, byte[] result, int bytesPerScanline)
{
// Insert a byte before the data.
byte[] result = new byte[bytesPerScanline + 1];
result[0] = 0;
Buffer.BlockCopy(scanline, 0, result, 1, bytesPerScanline);
return result;
}
}
}

13
src/ImageSharp/Formats/Png/Filters/PaethFilter.cs

@ -21,11 +21,9 @@ namespace ImageSharp.Formats
/// <param name="scanline">The scanline to decode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
{
// Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp))
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
{
@ -38,8 +36,6 @@ namespace ImageSharp.Formats
scan[x] = (byte)((scan[x] + PaethPredicator(left, above, upperLeft)) % 256);
}
}
return scanline;
}
/// <summary>
@ -47,13 +43,12 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel, int bytesPerScanline)
public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel, int bytesPerScanline)
{
// Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp))
byte[] result = new byte[bytesPerScanline + 1];
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
fixed (byte* res = result)
@ -69,8 +64,6 @@ namespace ImageSharp.Formats
res[x + 1] = (byte)((scan[x] - PaethPredicator(left, above, upperLeft)) % 256);
}
}
return result;
}
/// <summary>

13
src/ImageSharp/Formats/Png/Filters/SubFilter.cs

@ -17,11 +17,9 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="scanline">The scanline to decode</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Decode(byte[] scanline, int bytesPerPixel)
public static void Decode(byte[] scanline, int bytesPerPixel)
{
// Sub(x) + Raw(x-bpp)
fixed (byte* scan = scanline)
{
for (int x = 1; x < scanline.Length; x++)
@ -30,21 +28,18 @@ namespace ImageSharp.Formats
scan[x] = (byte)((scan[x] + priorRawByte) % 256);
}
}
return scanline;
}
/// <summary>
/// Encodes the scanline
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Encode(byte[] scanline, int bytesPerPixel, int bytesPerScanline)
public static void Encode(byte[] scanline, byte[] result, int bytesPerPixel, int bytesPerScanline)
{
// Sub(x) = Raw(x) - Raw(x-bpp)
byte[] result = new byte[bytesPerScanline + 1];
fixed (byte* scan = scanline)
fixed (byte* res = result)
{
@ -57,8 +52,6 @@ namespace ImageSharp.Formats
res[x + 1] = (byte)((scan[x] - priorRawByte) % 256);
}
}
return result;
}
}
}

13
src/ImageSharp/Formats/Png/Filters/UpFilter.cs

@ -17,11 +17,9 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="scanline">The scanline to decode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Decode(byte[] scanline, byte[] previousScanline)
public static void Decode(byte[] scanline, byte[] previousScanline)
{
// Up(x) + Prior(x)
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
{
@ -32,8 +30,6 @@ namespace ImageSharp.Formats
scan[x] = (byte)((scan[x] + above) % 256);
}
}
return scanline;
}
/// <summary>
@ -41,12 +37,11 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerScanline)
public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerScanline)
{
// Up(x) = Raw(x) - Prior(x)
byte[] result = new byte[bytesPerScanline + 1];
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
fixed (byte* res = result)
@ -60,8 +55,6 @@ namespace ImageSharp.Formats
res[x + 1] = (byte)((scan[x] - above) % 256);
}
}
return result;
}
}
}

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

@ -12,6 +12,8 @@ namespace ImageSharp.Formats
using System.Linq;
using System.Text;
using static ComparableExtensions;
/// <summary>
/// Performs the png decoding operation.
/// </summary>
@ -112,7 +114,8 @@ namespace ImageSharp.Formats
/// Thrown if the image is larger than the maximum allowable size.
/// </exception>
public void Decode<TColor, TPacked>(Image<TColor, TPacked> image, Stream stream)
where TColor : struct, IPackedPixel<TPacked> where TPacked : struct
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
Image<TColor, TPacked> currentImage = image;
this.currentStream = stream;
@ -130,39 +133,49 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Image does not end with end chunk.");
}
switch (currentChunk.Type)
try
{
case PngChunkTypes.Header:
this.ReadHeaderChunk(currentChunk.Data);
this.ValidateHeader();
break;
case PngChunkTypes.Physical:
this.ReadPhysicalChunk(currentImage, currentChunk.Data);
break;
case PngChunkTypes.Data:
dataStream.Write(currentChunk.Data, 0, currentChunk.Data.Length);
break;
case PngChunkTypes.Palette:
this.palette = currentChunk.Data;
image.Quality = this.palette.Length / 3;
break;
case PngChunkTypes.PaletteAlpha:
this.paletteAlpha = currentChunk.Data;
break;
case PngChunkTypes.Text:
this.ReadTextChunk(currentImage, currentChunk.Data);
break;
case PngChunkTypes.End:
isEndChunkReached = true;
break;
switch (currentChunk.Type)
{
case PngChunkTypes.Header:
this.ReadHeaderChunk(currentChunk.Data);
this.ValidateHeader();
break;
case PngChunkTypes.Physical:
this.ReadPhysicalChunk(currentImage, currentChunk.Data);
break;
case PngChunkTypes.Data:
dataStream.Write(currentChunk.Data, 0, currentChunk.Length);
break;
case PngChunkTypes.Palette:
byte[] pal = new byte[currentChunk.Length];
Buffer.BlockCopy(currentChunk.Data, 0, pal, 0, currentChunk.Length);
this.palette = pal;
image.Quality = pal.Length / 3;
break;
case PngChunkTypes.PaletteAlpha:
byte[] alpha = new byte[currentChunk.Length];
Buffer.BlockCopy(currentChunk.Data, 0, alpha, 0, currentChunk.Length);
this.paletteAlpha = alpha;
break;
case PngChunkTypes.Text:
this.ReadTextChunk(currentImage, currentChunk.Data, currentChunk.Length);
break;
case PngChunkTypes.End:
isEndChunkReached = true;
break;
}
}
finally
{
// Data is rented in ReadChunkData()
ArrayPool<byte>.Shared.Return(currentChunk.Data);
}
}
if (this.header.Width > image.MaxWidth || this.header.Height > image.MaxHeight)
{
throw new ArgumentOutOfRangeException(
$"The input png '{this.header.Width}x{this.header.Height}' is bigger than the "
+ $"max allowed size '{image.MaxWidth}x{image.MaxHeight}'");
throw new ArgumentOutOfRangeException($"The input png '{this.header.Width}x{this.header.Height}' is bigger than the max allowed size '{image.MaxWidth}x{image.MaxHeight}'");
}
image.InitPixels(this.header.Width, this.header.Height);
@ -182,7 +195,8 @@ namespace ImageSharp.Formats
/// <param name="image">The image to read to.</param>
/// <param name="data">The data containing physical data.</param>
private void ReadPhysicalChunk<TColor, TPacked>(Image<TColor, TPacked> image, byte[] data)
where TColor : struct, IPackedPixel<TPacked> where TPacked : struct
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
data.ReverseBytes(0, 4);
data.ReverseBytes(4, 4);
@ -243,7 +257,8 @@ namespace ImageSharp.Formats
/// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param>
/// <param name="pixels"> The pixel data.</param>
private void ReadScanlines<TColor, TPacked>(MemoryStream dataStream, PixelAccessor<TColor, TPacked> pixels)
where TColor : struct, IPackedPixel<TPacked> where TPacked : struct
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
this.bytesPerPixel = this.CalculateBytesPerPixel();
this.bytesPerScanline = this.CalculateScanlineLength() + 1;
@ -268,7 +283,8 @@ namespace ImageSharp.Formats
/// <param name="compressedStream">The compressed pixel data stream.</param>
/// <param name="pixels">The image pixel accessor.</param>
private void DecodePixelData<TColor, TPacked>(Stream compressedStream, PixelAccessor<TColor, TPacked> pixels)
where TColor : struct, IPackedPixel<TPacked> where TPacked : struct
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
byte[] previousScanline = ArrayPool<byte>.Shared.Rent(this.bytesPerScanline);
byte[] scanline = ArrayPool<byte>.Shared.Rent(this.bytesPerScanline);
@ -319,9 +335,7 @@ namespace ImageSharp.Formats
this.ProcessDefilteredScanline(scanline, y, pixels);
byte[] temp = previousScanline;
previousScanline = scanline;
scanline = temp;
Swap(ref scanline, ref previousScanline);
}
}
finally
@ -396,6 +410,10 @@ namespace ImageSharp.Formats
byte b = this.palette[pixelOffset + 2];
color.PackFromBytes(r, g, b, a);
}
else
{
color.PackFromBytes(0, 0, 0, 0);
}
pixels[x, row] = color;
}
@ -460,13 +478,14 @@ namespace ImageSharp.Formats
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="image">The image to decode to.</param>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
private void ReadTextChunk<TColor, TPacked>(Image<TColor, TPacked> image, byte[] data)
/// <param name="length">The maximum length to read.</param>
private void ReadTextChunk<TColor, TPacked>(Image<TColor, TPacked> image, byte[] data, int length)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
int zeroIndex = 0;
for (int i = 0; i < data.Length; i++)
for (int i = 0; i < length; i++)
{
if (data[i] == 0)
{
@ -476,7 +495,7 @@ namespace ImageSharp.Formats
}
string name = Encoding.Unicode.GetString(data, 0, zeroIndex);
string value = Encoding.Unicode.GetString(data, zeroIndex + 1, data.Length - zeroIndex - 1);
string value = Encoding.Unicode.GetString(data, zeroIndex + 1, length - zeroIndex - 1);
image.Properties.Add(new ImageProperty(name, value));
}
@ -577,7 +596,7 @@ namespace ImageSharp.Formats
Crc32 crc = new Crc32();
crc.Update(this.chunkTypeBuffer);
crc.Update(chunk.Data);
crc.Update(chunk.Data, 0, chunk.Length);
if (crc.Value != chunk.Crc)
{
@ -591,8 +610,8 @@ namespace ImageSharp.Formats
/// <param name="chunk">The chunk.</param>
private void ReadChunkData(PngChunk chunk)
{
// TODO: It might be possible to rent this but that could also lead to issues assigning the data to various properties
chunk.Data = new byte[chunk.Length];
// We rent the buffer here to return it afterwards in Decode()
chunk.Data = ArrayPool<byte>.Shared.Rent(chunk.Length);
this.currentStream.Read(chunk.Data, 0, chunk.Length);
}
@ -645,4 +664,4 @@ namespace ImageSharp.Formats
chunk.Length = BitConverter.ToInt32(this.chunkLengthBuffer, 0);
}
}
}
}

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

@ -13,6 +13,8 @@ namespace ImageSharp.Formats
using Quantizers;
using static ComparableExtensions;
/// <summary>
/// Performs the png encoding operation.
/// </summary>
@ -58,6 +60,26 @@ namespace ImageSharp.Formats
/// </summary>
private int bytesPerPixel;
/// <summary>
/// The buffer for the sub filter
/// </summary>
private byte[] sub;
/// <summary>
/// The buffer for the up filter
/// </summary>
private byte[] up;
/// <summary>
/// The buffer for the average filter
/// </summary>
private byte[] average;
/// <summary>
/// The buffer for the paeth filter
/// </summary>
private byte[] paeth;
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
@ -312,9 +334,10 @@ namespace ImageSharp.Formats
/// <param name="row">The row.</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="rawScanline">The raw scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline.</param>
/// <returns>The <see cref="T:byte[]"/></returns>
private byte[] EncodePixelRow<TColor, TPacked>(PixelAccessor<TColor, TPacked> pixels, int row, byte[] previousScanline, byte[] rawScanline, int bytesPerScanline)
private byte[] EncodePixelRow<TColor, TPacked>(PixelAccessor<TColor, TPacked> pixels, int row, byte[] previousScanline, byte[] rawScanline, byte[] result, int bytesPerScanline)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
@ -332,7 +355,7 @@ namespace ImageSharp.Formats
break;
}
return this.GetOptimalFilteredScanline(rawScanline, previousScanline, bytesPerScanline);
return this.GetOptimalFilteredScanline(rawScanline, previousScanline, result, bytesPerScanline);
}
/// <summary>
@ -341,45 +364,51 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="rawScanline">The raw scanline</param>
/// <param name="previousScanline">The previous scanline</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns>
private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int bytesPerScanline)
private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, byte[] result, int bytesPerScanline)
{
// Palette images don't compress well with adaptive filtering.
if (this.PngColorType == PngColorType.Palette)
{
return NoneFilter.Encode(rawScanline, bytesPerScanline);
NoneFilter.Encode(rawScanline, result, bytesPerScanline);
return result;
}
// TODO: it would be nice to avoid the memory allocation here.
Tuple<byte[], int>[] candidates = new Tuple<byte[], int>[4];
SubFilter.Encode(rawScanline, this.sub, this.bytesPerPixel, bytesPerScanline);
int currentTotalVariation = this.CalculateTotalVariation(this.sub, bytesPerScanline);
int lowestTotalVariation = currentTotalVariation;
byte[] sub = SubFilter.Encode(rawScanline, this.bytesPerPixel, bytesPerScanline);
candidates[0] = new Tuple<byte[], int>(sub, this.CalculateTotalVariation(sub, bytesPerScanline));
result = this.sub;
byte[] up = UpFilter.Encode(rawScanline, previousScanline, bytesPerScanline);
candidates[1] = new Tuple<byte[], int>(up, this.CalculateTotalVariation(up, bytesPerScanline));
UpFilter.Encode(rawScanline, previousScanline, this.up, bytesPerScanline);
currentTotalVariation = this.CalculateTotalVariation(this.up, bytesPerScanline);
byte[] average = AverageFilter.Encode(rawScanline, previousScanline, this.bytesPerPixel, bytesPerScanline);
candidates[2] = new Tuple<byte[], int>(average, this.CalculateTotalVariation(average, bytesPerScanline));
if (currentTotalVariation < lowestTotalVariation)
{
lowestTotalVariation = currentTotalVariation;
result = this.up;
}
byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, this.bytesPerPixel, bytesPerScanline);
candidates[3] = new Tuple<byte[], int>(paeth, this.CalculateTotalVariation(paeth, bytesPerScanline));
AverageFilter.Encode(rawScanline, previousScanline, this.average, this.bytesPerPixel, bytesPerScanline);
currentTotalVariation = this.CalculateTotalVariation(this.average, bytesPerScanline);
int lowestTotalVariation = int.MaxValue;
int lowestTotalVariationIndex = 0;
if (currentTotalVariation < lowestTotalVariation)
{
lowestTotalVariation = currentTotalVariation;
result = this.average;
}
for (int i = 0; i < candidates.Length; i++)
PaethFilter.Encode(rawScanline, previousScanline, this.paeth, this.bytesPerPixel, bytesPerScanline);
currentTotalVariation = this.CalculateTotalVariation(this.paeth, bytesPerScanline);
if (currentTotalVariation < lowestTotalVariation)
{
if (candidates[i].Item2 < lowestTotalVariation)
{
lowestTotalVariationIndex = i;
lowestTotalVariation = candidates[i].Item2;
}
result = this.paeth;
}
// ReSharper disable once RedundantAssignment
return candidates[lowestTotalVariationIndex].Item1;
return result;
}
/// <summary>
@ -592,6 +621,15 @@ namespace ImageSharp.Formats
byte[] previousScanline = ArrayPool<byte>.Shared.Rent(bytesPerScanline);
byte[] rawScanline = ArrayPool<byte>.Shared.Rent(bytesPerScanline);
int resultLength = bytesPerScanline + 1;
byte[] result = ArrayPool<byte>.Shared.Rent(resultLength);
if (this.PngColorType != PngColorType.Palette)
{
this.sub = ArrayPool<byte>.Shared.Rent(resultLength);
this.up = ArrayPool<byte>.Shared.Rent(resultLength);
this.average = ArrayPool<byte>.Shared.Rent(resultLength);
this.paeth = ArrayPool<byte>.Shared.Rent(resultLength);
}
byte[] buffer;
int bufferLength;
@ -603,12 +641,9 @@ namespace ImageSharp.Formats
{
for (int y = 0; y < this.height; y++)
{
deflateStream.Write(this.EncodePixelRow(pixels, y, previousScanline, rawScanline, bytesPerScanline), 0, resultLength);
deflateStream.Write(this.EncodePixelRow(pixels, y, previousScanline, rawScanline, result, bytesPerScanline), 0, resultLength);
// Do a bit of shuffling;
byte[] tmp = rawScanline;
rawScanline = previousScanline;
previousScanline = tmp;
Swap(ref rawScanline, ref previousScanline);
}
}
@ -620,6 +655,15 @@ namespace ImageSharp.Formats
memoryStream?.Dispose();
ArrayPool<byte>.Shared.Return(previousScanline);
ArrayPool<byte>.Shared.Return(rawScanline);
ArrayPool<byte>.Shared.Return(result);
if (this.PngColorType != PngColorType.Palette)
{
ArrayPool<byte>.Shared.Return(this.sub);
ArrayPool<byte>.Shared.Return(this.up);
ArrayPool<byte>.Shared.Return(this.average);
ArrayPool<byte>.Shared.Return(this.paeth);
}
}
// Store the chunks in repeated 64k blocks.

10
src/ImageSharp/IO/EndianBitConverter.cs

@ -20,9 +20,11 @@ namespace ImageSharp.IO
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:ElementsMustAppearInTheCorrectOrder", Justification = "Reviewed. Suppression is OK here. Better readability.")]
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "Reviewed. Suppression is OK here. Better readability.")]
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:StaticElementsMustAppearBeforeInstanceElements", Justification = "Reviewed. Suppression is OK here. Better readability.")]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:DoNotUseRegions", Justification = "Reviewed. Suppression is OK here. Better readability.")]
internal abstract class EndianBitConverter
{
#region Endianness of this converter
/// <summary>
/// Indicates the byte order ("endianness") in which data is converted using this class.
/// </summary>
@ -41,6 +43,7 @@ namespace ImageSharp.IO
#endregion
#region Factory properties
/// <summary>
/// The little-endian bit converter.
/// </summary>
@ -65,6 +68,7 @@ namespace ImageSharp.IO
#endregion
#region Double/primitive conversions
/// <summary>
/// Converts the specified double-precision floating point number to a
/// 64-bit signed integer. Note: the endianness of this converter does not
@ -115,6 +119,7 @@ namespace ImageSharp.IO
#endregion
#region To(PrimitiveType) conversions
/// <summary>
/// Returns a Boolean value converted from one byte at a specified position in a byte array.
/// </summary>
@ -279,6 +284,7 @@ namespace ImageSharp.IO
#endregion
#region ToString conversions
/// <summary>
/// Returns a String converted from the elements of a byte array.
/// </summary>
@ -326,6 +332,7 @@ namespace ImageSharp.IO
#endregion
#region Decimal conversions
/// <summary>
/// Returns a decimal value converted from sixteen bytes
/// at a specified position in a byte array.
@ -382,6 +389,7 @@ namespace ImageSharp.IO
#endregion
#region GetBytes conversions
/// <summary>
/// Returns an array with the given number of bytes formed
/// from the least significant bytes of the specified value.
@ -508,6 +516,7 @@ namespace ImageSharp.IO
#endregion
#region CopyBytes conversions
/// <summary>
/// Copies the given number of bytes from the least-specific
/// end of the specified value into the specified byte array, beginning
@ -669,6 +678,7 @@ namespace ImageSharp.IO
#endregion
#region Private struct used for Single/Int32 conversions
/// <summary>
/// Union used solely for the equivalent of DoubleToInt64Bits and vice versa.
/// </summary>

20
src/ImageSharp/Numerics/Rectangle.cs

@ -213,6 +213,16 @@ namespace ImageSharp
return !left.Equals(right);
}
/// <summary>
/// Returns the center point of the given <see cref="Rectangle"/>
/// </summary>
/// <param name="rectangle">The rectangle</param>
/// <returns><see cref="Point"/></returns>
public static Point Center(Rectangle rectangle)
{
return new Point(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2));
}
/// <summary>
/// Determines if the specfied point is contained within the rectangular region defined by
/// this <see cref="Rectangle"/>.
@ -229,16 +239,6 @@ namespace ImageSharp
&& y < this.Bottom;
}
/// <summary>
/// Returns the center point of the given <see cref="Rectangle"/>
/// </summary>
/// <param name="rectangle">The rectangle</param>
/// <returns><see cref="Point"/></returns>
public static Point Center(Rectangle rectangle)
{
return new Point(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2));
}
/// <inheritdoc/>
public override int GetHashCode()
{

82
src/ImageSharp/Profiles/Exif/ExifReader.cs

@ -15,8 +15,6 @@ namespace ImageSharp
/// </summary>
internal sealed class ExifReader
{
private delegate TDataType ConverterMethod<TDataType>(byte[] data);
private readonly Collection<ExifTag> invalidTags = new Collection<ExifTag>();
private byte[] exifData;
private uint currentIndex;
@ -25,6 +23,13 @@ namespace ImageSharp
private uint gpsOffset;
private uint startIndex;
private delegate TDataType ConverterMethod<TDataType>(byte[] data);
/// <summary>
/// Gets the invalid tags.
/// </summary>
public IEnumerable<ExifTag> InvalidTags => this.invalidTags;
/// <summary>
/// Gets the thumbnail length in the byte stream
/// </summary>
@ -112,10 +117,40 @@ namespace ImageSharp
return result;
}
/// <summary>
/// Gets the invalid tags.
/// </summary>
public IEnumerable<ExifTag> InvalidTags => this.invalidTags;
private static TDataType[] ToArray<TDataType>(ExifDataType dataType, byte[] data, ConverterMethod<TDataType> converter)
{
int dataTypeSize = (int)ExifValue.GetSize(dataType);
int length = data.Length / dataTypeSize;
TDataType[] result = new TDataType[length];
byte[] buffer = new byte[dataTypeSize];
for (int i = 0; i < length; i++)
{
Array.Copy(data, i * dataTypeSize, buffer, 0, dataTypeSize);
result.SetValue(converter(buffer), i);
}
return result;
}
private static byte ToByte(byte[] data)
{
return data[0];
}
private static string ToString(byte[] data)
{
string result = Encoding.UTF8.GetString(data, 0, data.Length);
int nullCharIndex = result.IndexOf('\0');
if (nullCharIndex != -1)
{
result = result.Substring(0, nullCharIndex);
}
return result;
}
/// <summary>
/// Adds the collection of EXIF values to the reader.
@ -369,29 +404,6 @@ namespace ImageSharp
}
}
private static TDataType[] ToArray<TDataType>(ExifDataType dataType, byte[] data, ConverterMethod<TDataType> converter)
{
int dataTypeSize = (int)ExifValue.GetSize(dataType);
int length = data.Length / dataTypeSize;
TDataType[] result = new TDataType[length];
byte[] buffer = new byte[dataTypeSize];
for (int i = 0; i < length; i++)
{
Array.Copy(data, i * dataTypeSize, buffer, 0, dataTypeSize);
result.SetValue(converter(buffer), i);
}
return result;
}
private static byte ToByte(byte[] data)
{
return data[0];
}
private double ToDouble(byte[] data)
{
if (!this.ValidateArray(data, 8))
@ -432,18 +444,6 @@ namespace ImageSharp
return BitConverter.ToSingle(data, 0);
}
private static string ToString(byte[] data)
{
string result = Encoding.UTF8.GetString(data, 0, data.Length);
int nullCharIndex = result.IndexOf('\0');
if (nullCharIndex != -1)
{
result = result.Substring(0, nullCharIndex);
}
return result;
}
private Rational ToRational(byte[] data)
{
if (!this.ValidateArray(data, 8, 4))

178
src/ImageSharp/Profiles/Exif/ExifValue.cs

@ -41,6 +41,24 @@ namespace ImageSharp
}
}
internal ExifValue(ExifTag tag, ExifDataType dataType, bool isArray)
{
this.Tag = tag;
this.DataType = dataType;
this.IsArray = isArray;
if (dataType == ExifDataType.Ascii)
{
this.IsArray = false;
}
}
internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray)
: this(tag, dataType, isArray)
{
this.exifValue = value;
}
/// <summary>
/// Gets the data type of the exif value.
/// </summary>
@ -81,6 +99,57 @@ namespace ImageSharp
}
}
internal bool HasValue
{
get
{
if (this.exifValue == null)
{
return false;
}
if (this.DataType == ExifDataType.Ascii)
{
return ((string)this.exifValue).Length > 0;
}
return true;
}
}
internal int Length
{
get
{
if (this.exifValue == null)
{
return 4;
}
int size = (int)(GetSize(this.DataType) * this.NumberOfComponents);
return size < 4 ? 4 : size;
}
}
internal int NumberOfComponents
{
get
{
if (this.DataType == ExifDataType.Ascii)
{
return Encoding.UTF8.GetBytes((string)this.exifValue).Length;
}
if (this.IsArray)
{
return ((Array)this.exifValue).Length;
}
return 1;
}
}
/// <summary>
/// Determines whether the specified ExifValue instances are considered equal.
/// </summary>
@ -173,75 +242,6 @@ namespace ImageSharp
return sb.ToString();
}
internal bool HasValue
{
get
{
if (this.exifValue == null)
{
return false;
}
if (this.DataType == ExifDataType.Ascii)
{
return ((string)this.exifValue).Length > 0;
}
return true;
}
}
internal int Length
{
get
{
if (this.exifValue == null)
{
return 4;
}
int size = (int)(GetSize(this.DataType) * this.NumberOfComponents);
return size < 4 ? 4 : size;
}
}
internal int NumberOfComponents
{
get
{
if (this.DataType == ExifDataType.Ascii)
{
return Encoding.UTF8.GetBytes((string)this.exifValue).Length;
}
if (this.IsArray)
{
return ((Array)this.exifValue).Length;
}
return 1;
}
}
internal ExifValue(ExifTag tag, ExifDataType dataType, bool isArray)
{
this.Tag = tag;
this.DataType = dataType;
this.IsArray = isArray;
if (dataType == ExifDataType.Ascii)
{
this.IsArray = false;
}
}
internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray)
: this(tag, dataType, isArray)
{
this.exifValue = value;
}
internal static ExifValue Create(ExifTag tag, object value)
{
Guard.IsFalse(tag == ExifTag.Unknown, nameof(tag), "Invalid Tag");
@ -574,6 +574,26 @@ namespace ImageSharp
}
}
private static ExifValue CreateNumber(ExifTag tag, Type type, bool isArray)
{
if (type == null || type == typeof(ushort))
{
return new ExifValue(tag, ExifDataType.Short, isArray);
}
if (type == typeof(short))
{
return new ExifValue(tag, ExifDataType.SignedShort, isArray);
}
if (type == typeof(uint))
{
return new ExifValue(tag, ExifDataType.Long, isArray);
}
return new ExifValue(tag, ExifDataType.SignedLong, isArray);
}
private void CheckValue(object value)
{
if (value == null)
@ -639,26 +659,6 @@ namespace ImageSharp
}
}
private static ExifValue CreateNumber(ExifTag tag, Type type, bool isArray)
{
if (type == null || type == typeof(ushort))
{
return new ExifValue(tag, ExifDataType.Short, isArray);
}
if (type == typeof(short))
{
return new ExifValue(tag, ExifDataType.SignedShort, isArray);
}
if (type == typeof(uint))
{
return new ExifValue(tag, ExifDataType.Long, isArray);
}
return new ExifValue(tag, ExifDataType.SignedLong, isArray);
}
private string ToString(object value)
{
string description = ExifTagDescriptionAttribute.GetDescription(this.Tag, value);

18
src/ImageSharp/Profiles/Exif/ExifWriter.cs

@ -12,6 +12,8 @@ namespace ImageSharp
internal sealed class ExifWriter
{
private const int StartIndex = 6;
private static readonly ExifTag[] IfdTags = new ExifTag[127]
{
ExifTag.SubfileType,
@ -274,8 +276,6 @@ namespace ImageSharp
ExifTag.GPSDifferential
};
private const int StartIndex = 6;
private ExifParts allowedParts;
private Collection<ExifValue> values;
private Collection<int> dataOffsets;
@ -379,6 +379,13 @@ namespace ImageSharp
return result;
}
private static int Write(byte[] source, byte[] destination, int offset)
{
Buffer.BlockCopy(source, 0, destination, offset, source.Length);
return offset + source.Length;
}
private int GetIndex(Collection<int> indexes, ExifTag tag)
{
foreach (int index in indexes)
@ -443,13 +450,6 @@ namespace ImageSharp
return length;
}
private static int Write(byte[] source, byte[] destination, int offset)
{
Buffer.BlockCopy(source, 0, destination, offset, source.Length);
return offset + source.Length;
}
private int WriteArray(ExifValue value, byte[] destination, int offset)
{
if (value.DataType == ExifDataType.Ascii)

34
src/ImageSharp/Samplers/Processors/RotateProcessor.cs

@ -33,23 +33,6 @@ namespace ImageSharp.Processors
/// </summary>
public bool Expand { get; set; } = true;
/// <inheritdoc/>
protected override void OnApply(ImageBase<TColor, TPacked> target, ImageBase<TColor, TPacked> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
const float Epsilon = .0001F;
if (Math.Abs(this.Angle) < Epsilon || Math.Abs(this.Angle - 90) < Epsilon || Math.Abs(this.Angle - 180) < Epsilon || Math.Abs(this.Angle - 270) < Epsilon)
{
return;
}
this.processMatrix = Point.CreateRotation(new Point(0, 0), -this.Angle);
if (this.Expand)
{
CreateNewTarget(target, sourceRectangle, this.processMatrix);
}
}
/// <inheritdoc/>
public override void Apply(ImageBase<TColor, TPacked> target, ImageBase<TColor, TPacked> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
@ -83,6 +66,23 @@ namespace ImageSharp.Processors
}
}
/// <inheritdoc/>
protected override void OnApply(ImageBase<TColor, TPacked> target, ImageBase<TColor, TPacked> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
const float Epsilon = .0001F;
if (Math.Abs(this.Angle) < Epsilon || Math.Abs(this.Angle - 90) < Epsilon || Math.Abs(this.Angle - 180) < Epsilon || Math.Abs(this.Angle - 270) < Epsilon)
{
return;
}
this.processMatrix = Point.CreateRotation(new Point(0, 0), -this.Angle);
if (this.Expand)
{
CreateNewTarget(target, sourceRectangle, this.processMatrix);
}
}
/// <summary>
/// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees.
/// </summary>

19
tests/ImageSharp.Tests/Formats/Png/PngTests.cs

@ -8,6 +8,7 @@ using ImageSharp.Formats;
namespace ImageSharp.Tests
{
using System.IO;
using System.Threading.Tasks;
using Formats;
@ -31,5 +32,23 @@ namespace ImageSharp.Tests
}
}
}
[Fact]
public void ImageCanSavePngInParallel()
{
string path = this.CreateOutputDirectory("Png");
Parallel.ForEach(
Files,
file =>
{
Image image = file.CreateImage();
using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png"))
{
image.Save(output, new PngFormat());
}
});
}
}
}
Loading…
Cancel
Save