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) public static byte[] ToArrayByBitsLength(this byte[] source, int bits)
{ {
Guard.NotNull(source, nameof(source)); Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThan(bits, 0, "bits"); Guard.MustBeGreaterThan(bits, 0, nameof(bits));
byte[] result; byte[] result;

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

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

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

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

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

@ -17,11 +17,9 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="scanline">The scanline to decode</param> /// <param name="scanline">The scanline to decode</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <returns>The <see cref="T:byte[]"/></returns> public static void Decode(byte[] scanline, int bytesPerPixel)
public static byte[] Decode(byte[] scanline, int bytesPerPixel)
{ {
// Sub(x) + Raw(x-bpp) // Sub(x) + Raw(x-bpp)
fixed (byte* scan = scanline) fixed (byte* scan = scanline)
{ {
for (int x = 1; x < scanline.Length; x++) for (int x = 1; x < scanline.Length; x++)
@ -30,21 +28,18 @@ namespace ImageSharp.Formats
scan[x] = (byte)((scan[x] + priorRawByte) % 256); scan[x] = (byte)((scan[x] + priorRawByte) % 256);
} }
} }
return scanline;
} }
/// <summary> /// <summary>
/// Encodes the scanline /// Encodes the scanline
/// </summary> /// </summary>
/// <param name="scanline">The scanline to encode</param> /// <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="bytesPerPixel">The bytes per pixel.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param> /// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns> public static void Encode(byte[] scanline, byte[] result, int bytesPerPixel, int bytesPerScanline)
public static byte[] Encode(byte[] scanline, int bytesPerPixel, int bytesPerScanline)
{ {
// Sub(x) = Raw(x) - Raw(x-bpp) // Sub(x) = Raw(x) - Raw(x-bpp)
byte[] result = new byte[bytesPerScanline + 1];
fixed (byte* scan = scanline) fixed (byte* scan = scanline)
fixed (byte* res = result) fixed (byte* res = result)
{ {
@ -57,8 +52,6 @@ namespace ImageSharp.Formats
res[x + 1] = (byte)((scan[x] - priorRawByte) % 256); 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> /// </summary>
/// <param name="scanline">The scanline to decode</param> /// <param name="scanline">The scanline to decode</param>
/// <param name="previousScanline">The previous scanline.</param> /// <param name="previousScanline">The previous scanline.</param>
/// <returns>The <see cref="T:byte[]"/></returns> public static void Decode(byte[] scanline, byte[] previousScanline)
public static byte[] Decode(byte[] scanline, byte[] previousScanline)
{ {
// Up(x) + Prior(x) // Up(x) + Prior(x)
fixed (byte* scan = scanline) fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline) fixed (byte* prev = previousScanline)
{ {
@ -32,8 +30,6 @@ namespace ImageSharp.Formats
scan[x] = (byte)((scan[x] + above) % 256); scan[x] = (byte)((scan[x] + above) % 256);
} }
} }
return scanline;
} }
/// <summary> /// <summary>
@ -41,12 +37,11 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="scanline">The scanline to encode</param> /// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous 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> /// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns> public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerScanline)
public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerScanline)
{ {
// Up(x) = Raw(x) - Prior(x) // Up(x) = Raw(x) - Prior(x)
byte[] result = new byte[bytesPerScanline + 1];
fixed (byte* scan = scanline) fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline) fixed (byte* prev = previousScanline)
fixed (byte* res = result) fixed (byte* res = result)
@ -60,8 +55,6 @@ namespace ImageSharp.Formats
res[x + 1] = (byte)((scan[x] - above) % 256); 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.Linq;
using System.Text; using System.Text;
using static ComparableExtensions;
/// <summary> /// <summary>
/// Performs the png decoding operation. /// Performs the png decoding operation.
/// </summary> /// </summary>
@ -112,7 +114,8 @@ namespace ImageSharp.Formats
/// Thrown if the image is larger than the maximum allowable size. /// Thrown if the image is larger than the maximum allowable size.
/// </exception> /// </exception>
public void Decode<TColor, TPacked>(Image<TColor, TPacked> image, Stream stream) 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; Image<TColor, TPacked> currentImage = image;
this.currentStream = stream; this.currentStream = stream;
@ -130,39 +133,49 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Image does not end with end chunk."); throw new ImageFormatException("Image does not end with end chunk.");
} }
switch (currentChunk.Type) try
{ {
case PngChunkTypes.Header: switch (currentChunk.Type)
this.ReadHeaderChunk(currentChunk.Data); {
this.ValidateHeader(); case PngChunkTypes.Header:
break; this.ReadHeaderChunk(currentChunk.Data);
case PngChunkTypes.Physical: this.ValidateHeader();
this.ReadPhysicalChunk(currentImage, currentChunk.Data); break;
break; case PngChunkTypes.Physical:
case PngChunkTypes.Data: this.ReadPhysicalChunk(currentImage, currentChunk.Data);
dataStream.Write(currentChunk.Data, 0, currentChunk.Data.Length); break;
break; case PngChunkTypes.Data:
case PngChunkTypes.Palette: dataStream.Write(currentChunk.Data, 0, currentChunk.Length);
this.palette = currentChunk.Data; break;
image.Quality = this.palette.Length / 3; case PngChunkTypes.Palette:
break; byte[] pal = new byte[currentChunk.Length];
case PngChunkTypes.PaletteAlpha: Buffer.BlockCopy(currentChunk.Data, 0, pal, 0, currentChunk.Length);
this.paletteAlpha = currentChunk.Data; this.palette = pal;
break; image.Quality = pal.Length / 3;
case PngChunkTypes.Text: break;
this.ReadTextChunk(currentImage, currentChunk.Data); case PngChunkTypes.PaletteAlpha:
break; byte[] alpha = new byte[currentChunk.Length];
case PngChunkTypes.End: Buffer.BlockCopy(currentChunk.Data, 0, alpha, 0, currentChunk.Length);
isEndChunkReached = true; this.paletteAlpha = alpha;
break; 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) if (this.header.Width > image.MaxWidth || this.header.Height > image.MaxHeight)
{ {
throw new ArgumentOutOfRangeException( 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}'");
$"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); 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="image">The image to read to.</param>
/// <param name="data">The data containing physical data.</param> /// <param name="data">The data containing physical data.</param>
private void ReadPhysicalChunk<TColor, TPacked>(Image<TColor, TPacked> image, byte[] data) 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(0, 4);
data.ReverseBytes(4, 4); data.ReverseBytes(4, 4);
@ -243,7 +257,8 @@ namespace ImageSharp.Formats
/// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param> /// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param>
/// <param name="pixels"> The pixel data.</param> /// <param name="pixels"> The pixel data.</param>
private void ReadScanlines<TColor, TPacked>(MemoryStream dataStream, PixelAccessor<TColor, TPacked> pixels) 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.bytesPerPixel = this.CalculateBytesPerPixel();
this.bytesPerScanline = this.CalculateScanlineLength() + 1; this.bytesPerScanline = this.CalculateScanlineLength() + 1;
@ -268,7 +283,8 @@ namespace ImageSharp.Formats
/// <param name="compressedStream">The compressed pixel data stream.</param> /// <param name="compressedStream">The compressed pixel data stream.</param>
/// <param name="pixels">The image pixel accessor.</param> /// <param name="pixels">The image pixel accessor.</param>
private void DecodePixelData<TColor, TPacked>(Stream compressedStream, PixelAccessor<TColor, TPacked> pixels) 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[] previousScanline = ArrayPool<byte>.Shared.Rent(this.bytesPerScanline);
byte[] scanline = 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); this.ProcessDefilteredScanline(scanline, y, pixels);
byte[] temp = previousScanline; Swap(ref scanline, ref previousScanline);
previousScanline = scanline;
scanline = temp;
} }
} }
finally finally
@ -396,6 +410,10 @@ namespace ImageSharp.Formats
byte b = this.palette[pixelOffset + 2]; byte b = this.palette[pixelOffset + 2];
color.PackFromBytes(r, g, b, a); color.PackFromBytes(r, g, b, a);
} }
else
{
color.PackFromBytes(0, 0, 0, 0);
}
pixels[x, row] = color; pixels[x, row] = color;
} }
@ -460,13 +478,14 @@ namespace ImageSharp.Formats
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam> /// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="image">The image to decode to.</param> /// <param name="image">The image to decode to.</param>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</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 TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
int zeroIndex = 0; int zeroIndex = 0;
for (int i = 0; i < data.Length; i++) for (int i = 0; i < length; i++)
{ {
if (data[i] == 0) if (data[i] == 0)
{ {
@ -476,7 +495,7 @@ namespace ImageSharp.Formats
} }
string name = Encoding.Unicode.GetString(data, 0, zeroIndex); 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)); image.Properties.Add(new ImageProperty(name, value));
} }
@ -577,7 +596,7 @@ namespace ImageSharp.Formats
Crc32 crc = new Crc32(); Crc32 crc = new Crc32();
crc.Update(this.chunkTypeBuffer); crc.Update(this.chunkTypeBuffer);
crc.Update(chunk.Data); crc.Update(chunk.Data, 0, chunk.Length);
if (crc.Value != chunk.Crc) if (crc.Value != chunk.Crc)
{ {
@ -591,8 +610,8 @@ namespace ImageSharp.Formats
/// <param name="chunk">The chunk.</param> /// <param name="chunk">The chunk.</param>
private void ReadChunkData(PngChunk chunk) 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 // We rent the buffer here to return it afterwards in Decode()
chunk.Data = new byte[chunk.Length]; chunk.Data = ArrayPool<byte>.Shared.Rent(chunk.Length);
this.currentStream.Read(chunk.Data, 0, chunk.Length); this.currentStream.Read(chunk.Data, 0, chunk.Length);
} }
@ -645,4 +664,4 @@ namespace ImageSharp.Formats
chunk.Length = BitConverter.ToInt32(this.chunkLengthBuffer, 0); chunk.Length = BitConverter.ToInt32(this.chunkLengthBuffer, 0);
} }
} }
} }

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

@ -13,6 +13,8 @@ namespace ImageSharp.Formats
using Quantizers; using Quantizers;
using static ComparableExtensions;
/// <summary> /// <summary>
/// Performs the png encoding operation. /// Performs the png encoding operation.
/// </summary> /// </summary>
@ -58,6 +60,26 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private int bytesPerPixel; 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> /// <summary>
/// Gets or sets the quality of output for images. /// Gets or sets the quality of output for images.
/// </summary> /// </summary>
@ -312,9 +334,10 @@ namespace ImageSharp.Formats
/// <param name="row">The row.</param> /// <param name="row">The row.</param>
/// <param name="previousScanline">The previous scanline.</param> /// <param name="previousScanline">The previous scanline.</param>
/// <param name="rawScanline">The raw 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> /// <param name="bytesPerScanline">The number of bytes per scanline.</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <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 TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
@ -332,7 +355,7 @@ namespace ImageSharp.Formats
break; break;
} }
return this.GetOptimalFilteredScanline(rawScanline, previousScanline, bytesPerScanline); return this.GetOptimalFilteredScanline(rawScanline, previousScanline, result, bytesPerScanline);
} }
/// <summary> /// <summary>
@ -341,45 +364,51 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="rawScanline">The raw scanline</param> /// <param name="rawScanline">The raw scanline</param>
/// <param name="previousScanline">The previous 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> /// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <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. // Palette images don't compress well with adaptive filtering.
if (this.PngColorType == PngColorType.Palette) 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. SubFilter.Encode(rawScanline, this.sub, this.bytesPerPixel, bytesPerScanline);
Tuple<byte[], int>[] candidates = new Tuple<byte[], int>[4]; int currentTotalVariation = this.CalculateTotalVariation(this.sub, bytesPerScanline);
int lowestTotalVariation = currentTotalVariation;
byte[] sub = SubFilter.Encode(rawScanline, this.bytesPerPixel, bytesPerScanline); result = this.sub;
candidates[0] = new Tuple<byte[], int>(sub, this.CalculateTotalVariation(sub, bytesPerScanline));
byte[] up = UpFilter.Encode(rawScanline, previousScanline, bytesPerScanline); UpFilter.Encode(rawScanline, previousScanline, this.up, bytesPerScanline);
candidates[1] = new Tuple<byte[], int>(up, this.CalculateTotalVariation(up, bytesPerScanline)); currentTotalVariation = this.CalculateTotalVariation(this.up, bytesPerScanline);
byte[] average = AverageFilter.Encode(rawScanline, previousScanline, this.bytesPerPixel, bytesPerScanline); if (currentTotalVariation < lowestTotalVariation)
candidates[2] = new Tuple<byte[], int>(average, this.CalculateTotalVariation(average, bytesPerScanline)); {
lowestTotalVariation = currentTotalVariation;
result = this.up;
}
byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, this.bytesPerPixel, bytesPerScanline); AverageFilter.Encode(rawScanline, previousScanline, this.average, this.bytesPerPixel, bytesPerScanline);
candidates[3] = new Tuple<byte[], int>(paeth, this.CalculateTotalVariation(paeth, bytesPerScanline)); currentTotalVariation = this.CalculateTotalVariation(this.average, bytesPerScanline);
int lowestTotalVariation = int.MaxValue; if (currentTotalVariation < lowestTotalVariation)
int lowestTotalVariationIndex = 0; {
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) result = this.paeth;
{
lowestTotalVariationIndex = i;
lowestTotalVariation = candidates[i].Item2;
}
} }
// ReSharper disable once RedundantAssignment return result;
return candidates[lowestTotalVariationIndex].Item1;
} }
/// <summary> /// <summary>
@ -592,6 +621,15 @@ namespace ImageSharp.Formats
byte[] previousScanline = ArrayPool<byte>.Shared.Rent(bytesPerScanline); byte[] previousScanline = ArrayPool<byte>.Shared.Rent(bytesPerScanline);
byte[] rawScanline = ArrayPool<byte>.Shared.Rent(bytesPerScanline); byte[] rawScanline = ArrayPool<byte>.Shared.Rent(bytesPerScanline);
int resultLength = bytesPerScanline + 1; 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; byte[] buffer;
int bufferLength; int bufferLength;
@ -603,12 +641,9 @@ namespace ImageSharp.Formats
{ {
for (int y = 0; y < this.height; y++) 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; Swap(ref rawScanline, ref previousScanline);
byte[] tmp = rawScanline;
rawScanline = previousScanline;
previousScanline = tmp;
} }
} }
@ -620,6 +655,15 @@ namespace ImageSharp.Formats
memoryStream?.Dispose(); memoryStream?.Dispose();
ArrayPool<byte>.Shared.Return(previousScanline); ArrayPool<byte>.Shared.Return(previousScanline);
ArrayPool<byte>.Shared.Return(rawScanline); 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. // 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", "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", "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.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 internal abstract class EndianBitConverter
{ {
#region Endianness of this converter #region Endianness of this converter
/// <summary> /// <summary>
/// Indicates the byte order ("endianness") in which data is converted using this class. /// Indicates the byte order ("endianness") in which data is converted using this class.
/// </summary> /// </summary>
@ -41,6 +43,7 @@ namespace ImageSharp.IO
#endregion #endregion
#region Factory properties #region Factory properties
/// <summary> /// <summary>
/// The little-endian bit converter. /// The little-endian bit converter.
/// </summary> /// </summary>
@ -65,6 +68,7 @@ namespace ImageSharp.IO
#endregion #endregion
#region Double/primitive conversions #region Double/primitive conversions
/// <summary> /// <summary>
/// Converts the specified double-precision floating point number to a /// Converts the specified double-precision floating point number to a
/// 64-bit signed integer. Note: the endianness of this converter does not /// 64-bit signed integer. Note: the endianness of this converter does not
@ -115,6 +119,7 @@ namespace ImageSharp.IO
#endregion #endregion
#region To(PrimitiveType) conversions #region To(PrimitiveType) conversions
/// <summary> /// <summary>
/// Returns a Boolean value converted from one byte at a specified position in a byte array. /// Returns a Boolean value converted from one byte at a specified position in a byte array.
/// </summary> /// </summary>
@ -279,6 +284,7 @@ namespace ImageSharp.IO
#endregion #endregion
#region ToString conversions #region ToString conversions
/// <summary> /// <summary>
/// Returns a String converted from the elements of a byte array. /// Returns a String converted from the elements of a byte array.
/// </summary> /// </summary>
@ -326,6 +332,7 @@ namespace ImageSharp.IO
#endregion #endregion
#region Decimal conversions #region Decimal conversions
/// <summary> /// <summary>
/// Returns a decimal value converted from sixteen bytes /// Returns a decimal value converted from sixteen bytes
/// at a specified position in a byte array. /// at a specified position in a byte array.
@ -382,6 +389,7 @@ namespace ImageSharp.IO
#endregion #endregion
#region GetBytes conversions #region GetBytes conversions
/// <summary> /// <summary>
/// Returns an array with the given number of bytes formed /// Returns an array with the given number of bytes formed
/// from the least significant bytes of the specified value. /// from the least significant bytes of the specified value.
@ -508,6 +516,7 @@ namespace ImageSharp.IO
#endregion #endregion
#region CopyBytes conversions #region CopyBytes conversions
/// <summary> /// <summary>
/// Copies the given number of bytes from the least-specific /// Copies the given number of bytes from the least-specific
/// end of the specified value into the specified byte array, beginning /// end of the specified value into the specified byte array, beginning
@ -669,6 +678,7 @@ namespace ImageSharp.IO
#endregion #endregion
#region Private struct used for Single/Int32 conversions #region Private struct used for Single/Int32 conversions
/// <summary> /// <summary>
/// Union used solely for the equivalent of DoubleToInt64Bits and vice versa. /// Union used solely for the equivalent of DoubleToInt64Bits and vice versa.
/// </summary> /// </summary>

20
src/ImageSharp/Numerics/Rectangle.cs

@ -213,6 +213,16 @@ namespace ImageSharp
return !left.Equals(right); 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> /// <summary>
/// Determines if the specfied point is contained within the rectangular region defined by /// Determines if the specfied point is contained within the rectangular region defined by
/// this <see cref="Rectangle"/>. /// this <see cref="Rectangle"/>.
@ -229,16 +239,6 @@ namespace ImageSharp
&& y < this.Bottom; && 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/> /// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode()
{ {

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

@ -15,8 +15,6 @@ namespace ImageSharp
/// </summary> /// </summary>
internal sealed class ExifReader internal sealed class ExifReader
{ {
private delegate TDataType ConverterMethod<TDataType>(byte[] data);
private readonly Collection<ExifTag> invalidTags = new Collection<ExifTag>(); private readonly Collection<ExifTag> invalidTags = new Collection<ExifTag>();
private byte[] exifData; private byte[] exifData;
private uint currentIndex; private uint currentIndex;
@ -25,6 +23,13 @@ namespace ImageSharp
private uint gpsOffset; private uint gpsOffset;
private uint startIndex; private uint startIndex;
private delegate TDataType ConverterMethod<TDataType>(byte[] data);
/// <summary>
/// Gets the invalid tags.
/// </summary>
public IEnumerable<ExifTag> InvalidTags => this.invalidTags;
/// <summary> /// <summary>
/// Gets the thumbnail length in the byte stream /// Gets the thumbnail length in the byte stream
/// </summary> /// </summary>
@ -112,10 +117,40 @@ namespace ImageSharp
return result; return result;
} }
/// <summary> private static TDataType[] ToArray<TDataType>(ExifDataType dataType, byte[] data, ConverterMethod<TDataType> converter)
/// Gets the invalid tags. {
/// </summary> int dataTypeSize = (int)ExifValue.GetSize(dataType);
public IEnumerable<ExifTag> InvalidTags => this.invalidTags; 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> /// <summary>
/// Adds the collection of EXIF values to the reader. /// 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) private double ToDouble(byte[] data)
{ {
if (!this.ValidateArray(data, 8)) if (!this.ValidateArray(data, 8))
@ -432,18 +444,6 @@ namespace ImageSharp
return BitConverter.ToSingle(data, 0); 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) private Rational ToRational(byte[] data)
{ {
if (!this.ValidateArray(data, 8, 4)) 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> /// <summary>
/// Gets the data type of the exif value. /// Gets the data type of the exif value.
/// </summary> /// </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> /// <summary>
/// Determines whether the specified ExifValue instances are considered equal. /// Determines whether the specified ExifValue instances are considered equal.
/// </summary> /// </summary>
@ -173,75 +242,6 @@ namespace ImageSharp
return sb.ToString(); 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) internal static ExifValue Create(ExifTag tag, object value)
{ {
Guard.IsFalse(tag == ExifTag.Unknown, nameof(tag), "Invalid Tag"); 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) private void CheckValue(object value)
{ {
if (value == null) 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) private string ToString(object value)
{ {
string description = ExifTagDescriptionAttribute.GetDescription(this.Tag, 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 internal sealed class ExifWriter
{ {
private const int StartIndex = 6;
private static readonly ExifTag[] IfdTags = new ExifTag[127] private static readonly ExifTag[] IfdTags = new ExifTag[127]
{ {
ExifTag.SubfileType, ExifTag.SubfileType,
@ -274,8 +276,6 @@ namespace ImageSharp
ExifTag.GPSDifferential ExifTag.GPSDifferential
}; };
private const int StartIndex = 6;
private ExifParts allowedParts; private ExifParts allowedParts;
private Collection<ExifValue> values; private Collection<ExifValue> values;
private Collection<int> dataOffsets; private Collection<int> dataOffsets;
@ -379,6 +379,13 @@ namespace ImageSharp
return result; 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) private int GetIndex(Collection<int> indexes, ExifTag tag)
{ {
foreach (int index in indexes) foreach (int index in indexes)
@ -443,13 +450,6 @@ namespace ImageSharp
return length; 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) private int WriteArray(ExifValue value, byte[] destination, int offset)
{ {
if (value.DataType == ExifDataType.Ascii) if (value.DataType == ExifDataType.Ascii)

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

@ -33,23 +33,6 @@ namespace ImageSharp.Processors
/// </summary> /// </summary>
public bool Expand { get; set; } = true; 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/> /// <inheritdoc/>
public override void Apply(ImageBase<TColor, TPacked> target, ImageBase<TColor, TPacked> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) 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> /// <summary>
/// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees. /// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees.
/// </summary> /// </summary>

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

@ -8,6 +8,7 @@ using ImageSharp.Formats;
namespace ImageSharp.Tests namespace ImageSharp.Tests
{ {
using System.IO; using System.IO;
using System.Threading.Tasks;
using Formats; 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