Browse Source

merge with upstream

pull/292/head
Nikita Balabaev 8 years ago
parent
commit
a44ceb8b8e
  1. 32
      README.md
  2. 14
      src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs
  3. 194
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  4. 21
      src/ImageSharp/Formats/Gif/FrameDecodingMode.cs
  5. 5
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  6. 61
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  7. 10
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  8. 9
      src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
  9. 49
      src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs
  10. 4
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  11. 13
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  12. 25
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
  13. 123
      src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
  14. 1
      src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs
  15. 4
      src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs
  16. 1
      src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs
  17. 46
      tests/ImageSharp.Tests/Drawing/DrawImageTest.cs
  18. 5
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  19. 37
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  20. 54
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
  21. 1
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  22. 7
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  23. 95
      tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs
  24. 12
      tests/ImageSharp.Tests/TestImages.cs
  25. BIN
      tests/Images/Input/Bmp/RunLengthEncoded-inverted.bmp
  26. BIN
      tests/Images/Input/Bmp/RunLengthEncoded.bmp
  27. BIN
      tests/Images/Input/Bmp/bpp8.bmp
  28. BIN
      tests/Images/Input/Bmp/test8-inverted.bmp
  29. BIN
      tests/Images/Input/Bmp/test8.bmp
  30. BIN
      tests/Images/Input/Png/big-corrupted-chunk.png

32
README.md

@ -1,13 +1,19 @@
[![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/SixLabors/ImageSharp/master/APACHE-2.0-LICENSE.txt)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Twitter](https://img.shields.io/twitter/url/https/github.com/SixLabors/ImageSharp.svg?style=social)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors)
[![OpenCollective](https://opencollective.com/imagesharp/backers/badge.svg)](#backers)
[![OpenCollective](https://opencollective.com/imagesharp/sponsors/badge.svg)](#sponsors)
# <img src="https://raw.githubusercontent.com/SixLabors/ImageSharp/master/build/icons/imagesharp-logo-256.png" alt="ImageSharp" width="52"/> ImageSharp
**ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. We have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. Gone are system-wide process-locks; ImageSharp images are thread-safe and fully supported in web environments.
<h1 align="center">
<img src="https://raw.githubusercontent.com/SixLabors/ImageSharp/master/build/icons/imagesharp-logo-256.png" alt="ImageSharp" width="175"/>
<br>
ImageSharp
<br>
<br>
<a href="https://raw.githubusercontent.com/SixLabors/ImageSharp/master/APACHE-2.0-LICENSE.txt"><img src="https://camo.githubusercontent.com/8f54547853cfad57acfc8e06e6008cc296cda34d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322d626c75652e737667" alt="GitHub license" data-canonical-src="https://img.shields.io/badge/license-Apache%202-blue.svg" style="max-width:100%;"></a>
<a href="https://gitter.im/ImageSharp/General?utm_source=badge&amp;utm_medium=badge&amp;utm_campaign=pr-badge&amp;utm_content=badge"><img src="https://camo.githubusercontent.com/da2edb525cde1455a622c58c0effc3a90b9a181c/68747470733a2f2f6261646765732e6769747465722e696d2f4a6f696e253230436861742e737667" alt="Gitter" data-canonical-src="https://badges.gitter.im/Join%20Chat.svg" style="max-width:100%;"></a>
<a href="https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&amp;text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&amp;url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&amp;via=sixlabors"><img src="https://camo.githubusercontent.com/aed174887b7d1f0a0877dd0a68c3872bc54d2fff/68747470733a2f2f696d672e736869656c64732e696f2f747769747465722f75726c2f68747470732f6769746875622e636f6d2f5369784c61626f72732f496d61676553686172702e7376673f7374796c653d736f6369616c" alt="Twitter" data-canonical-src="https://img.shields.io/twitter/url/https/github.com/SixLabors/ImageSharp.svg?style=social" style="max-width:100%;"></a>
<a href="#backers"><img src="https://camo.githubusercontent.com/c9af371c98b22cb92323a385a267a1215c454663/68747470733a2f2f6f70656e636f6c6c6563746976652e636f6d2f696d61676573686172702f6261636b6572732f62616467652e737667" alt="OpenCollective" data-canonical-src="https://opencollective.com/imagesharp/backers/badge.svg" style="max-width:100%;"></a>
<a href="#sponsors"><img src="https://camo.githubusercontent.com/5a9ae612f8e7fb5207f51a605f025770bfa9a80e/68747470733a2f2f6f70656e636f6c6c6563746976652e636f6d2f696d61676573686172702f73706f6e736f72732f62616467652e737667" alt="OpenCollective" data-canonical-src="https://opencollective.com/imagesharp/sponsors/badge.svg" style="max-width:100%;"></a>
</h1>
### **ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API.
Without the use of `System.Drawing` we have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. Gone are system-wide process-locks; ImageSharp images are thread-safe and fully supported in web environments.
Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and embedded/IoT scenarios.
@ -111,11 +117,7 @@ git clone https://github.com/SixLabors/ImageSharp
### How can you help?
Please... Spread the word, contribute algorithms, submit performance improvements, unit tests.
Performance is a biggie, if you know anything about the `System.Numerics.Vectors` types and can apply some fancy new stuff with that it would be awesome.
There's a lot of developers out there who could write this stuff a lot better and faster than I and I would love to see what we collectively can come up with so please, if you can help in any way it would be most welcome and benificial for all.
Please... Spread the word, contribute algorithms, submit performance improvements, unit tests, no input is too little.
### The ImageSharp Team

14
src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Helpers;
@ -42,7 +41,7 @@ namespace SixLabors.ImageSharp.Drawing.Processors
/// <summary>
/// Gets the image to blend.
/// </summary>
public Image<TPixel> Image { get; private set; }
public Image<TPixel> Image { get; }
/// <summary>
/// Gets the alpha percentage value.
@ -73,10 +72,11 @@ namespace SixLabors.ImageSharp.Drawing.Processors
}
// Align start/end positions.
Rectangle bounds = this.Image.Bounds();
Rectangle bounds = targetImage.Bounds();
int minX = Math.Max(this.Location.X, sourceRectangle.X);
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width);
maxX = Math.Min(this.Location.X + this.Size.Width, maxX);
int targetX = minX - this.Location.X;
int minY = Math.Max(this.Location.Y, sourceRectangle.Y);
int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom);
@ -84,9 +84,7 @@ namespace SixLabors.ImageSharp.Drawing.Processors
maxY = Math.Min(this.Location.Y + this.Size.Height, maxY);
int width = maxX - minX;
using (Buffer<float> amount = new Buffer<float>(width))
using (PixelAccessor<TPixel> toBlendPixels = targetImage.Lock())
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
using (var amount = new Buffer<float>(width))
{
for (int i = 0; i < width; i++)
{
@ -99,8 +97,8 @@ namespace SixLabors.ImageSharp.Drawing.Processors
configuration.ParallelOptions,
y =>
{
Span<TPixel> background = sourcePixels.GetRowSpan(y).Slice(minX, width);
Span<TPixel> foreground = toBlendPixels.GetRowSpan(y - this.Location.Y).Slice(0, width);
Span<TPixel> background = source.GetPixelRowSpan(y).Slice(minX, width);
Span<TPixel> foreground = targetImage.GetPixelRowSpan(y - this.Location.Y).Slice(targetX, width);
this.blender.Blend(background, background, foreground, amount);
});
}

194
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -29,6 +29,26 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private const int Rgb16BMask = 0x0000001F;
/// <summary>
/// RLE8 flag value that indicates following byte has special meaning
/// </summary>
private const int RleCommand = 0x00;
/// <summary>
/// RLE8 flag value marking end of a scan line
/// </summary>
private const int RleEndOfLine = 0x00;
/// <summary>
/// RLE8 flag value marking end of bitmap data
/// </summary>
private const int RleEndOfBitmap = 0x01;
/// <summary>
/// RLE8 flag value marking the start of [x,y] offset instruction
/// </summary>
private const int RleDelta = 0x02;
/// <summary>
/// The stream to decode from.
/// </summary>
@ -128,7 +148,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
+ $"bigger then the max allowed size '{int.MaxValue}x{int.MaxValue}'");
}
Image<TPixel> image = new Image<TPixel>(this.configuration, this.infoHeader.Width, this.infoHeader.Height);
var image = new Image<TPixel>(this.configuration, this.infoHeader.Width, this.infoHeader.Height);
using (PixelAccessor<TPixel> pixels = image.Lock())
{
switch (this.infoHeader.Compression)
@ -151,6 +171,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.ReadRgbPalette(pixels, palette, this.infoHeader.Width, this.infoHeader.Height, this.infoHeader.BitsPerPixel, inverted);
}
break;
case BmpCompression.RLE8:
this.ReadRle8(pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted);
break;
default:
throw new NotSupportedException("Does not support this kind of bitmap files.");
@ -172,6 +196,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
/// <returns>The <see cref="int"/> representing the inverted value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Invert(int y, int height, bool inverted)
{
int row;
@ -208,6 +233,116 @@ namespace SixLabors.ImageSharp.Formats.Bmp
return padding;
}
/// <summary>
/// Looks up color values and builds the image from de-compressed RLE8 data.
/// Compresssed RLE8 stream is uncompressed by <see cref="UncompressRle8(int, Span{byte})"/>
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The <see cref="PixelAccessor{TPixel}"/> to assign the palette to.</param>
/// <param name="colors">The <see cref="T:byte[]"/> containing the colors.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRle8<TPixel>(PixelAccessor<TPixel> pixels, byte[] colors, int width, int height, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
var color = default(TPixel);
var rgba = new Rgba32(0, 0, 0, 255);
using (var buffer = Buffer2D<byte>.CreateClean(width, height))
{
this.UncompressRle8(width, buffer);
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
Span<byte> bufferRow = buffer.GetRowSpan(y);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
for (int x = 0; x < width; x++)
{
rgba.Bgr = Unsafe.As<byte, Bgr24>(ref colors[bufferRow[x] * 4]);
color.PackFromRgba32(rgba);
pixelRow[x] = color;
}
}
}
}
/// <summary>
/// Produce uncompressed bitmap data from RLE8 stream
/// </summary>
/// <remarks>
/// RLE8 is a 2-byte run-length encoding
/// <br/>If first byte is 0, the second byte may have special meaning
/// <br/>Otherwise, first byte is the length of the run and second byte is the color for the run
/// </remarks>
/// <param name="w">The width of the bitmap.</param>
/// <param name="buffer">Buffer for uncompressed data.</param>
private void UncompressRle8(int w, Span<byte> buffer)
{
byte[] cmd = new byte[2];
int count = 0;
while (count < buffer.Length)
{
if (this.currentStream.Read(cmd, 0, cmd.Length) != 2)
{
throw new Exception("Failed to read 2 bytes from stream");
}
if (cmd[0] == RleCommand)
{
switch (cmd[1])
{
case RleEndOfBitmap:
return;
case RleEndOfLine:
int extra = count % w;
if (extra > 0)
{
count += w - extra;
}
break;
case RleDelta:
int dx = this.currentStream.ReadByte();
int dy = this.currentStream.ReadByte();
count += (w * dy) + dx;
break;
default:
// If the second byte > 2, signals 'absolute mode'
// Take this number of bytes from the stream as uncompressed data
int length = cmd[1];
int copyLength = length;
// Absolute mode data is aligned to two-byte word-boundary
length += length & 1;
byte[] run = new byte[length];
this.currentStream.Read(run, 0, run.Length);
for (int i = 0; i < copyLength; i++)
{
buffer[count++] = run[i];
}
break;
}
}
else
{
for (int i = 0; i < cmd[0]; i++)
{
buffer[count++] = cmd[1];
}
}
}
}
/// <summary>
/// Reads the color palette from the stream.
/// </summary>
@ -236,35 +371,36 @@ namespace SixLabors.ImageSharp.Formats.Bmp
padding = 4 - padding;
}
byte[] row = new byte[arrayWidth + padding];
TPixel color = default(TPixel);
Rgba32 rgba = default(Rgba32);
for (int y = 0; y < height; y++)
using (var row = Buffer<byte>.CreateClean(arrayWidth + padding))
{
int newY = Invert(y, height, inverted);
this.currentStream.Read(row, 0, row.Length);
int offset = 0;
Span<TPixel> pixelRow = pixels.GetRowSpan(y);
var color = default(TPixel);
var rgba = new Rgba32(0, 0, 0, 255);
// TODO: Could use PixelOperations here!
for (int x = 0; x < arrayWidth; x++)
for (int y = 0; y < height; y++)
{
int colOffset = x * ppb;
int newY = Invert(y, height, inverted);
this.currentStream.Read(row.Array, 0, row.Length);
int offset = 0;
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
for (int shift = 0; shift < ppb && (x + shift) < width; shift++)
// TODO: Could use PixelOperations here!
for (int x = 0; x < arrayWidth; x++)
{
int colorIndex = ((row[offset] >> (8 - bits - (shift * bits))) & mask) * 4;
int newX = colOffset + shift;
int colOffset = x * ppb;
// Stored in b-> g-> r order.
rgba.Bgr = Unsafe.As<byte, Bgr24>(ref colors[colorIndex]);
color.PackFromRgba32(rgba);
pixelRow[newX] = color;
}
for (int shift = 0; shift < ppb && (x + shift) < width; shift++)
{
int colorIndex = ((row[offset] >> (8 - bits - (shift * bits))) & mask) * 4;
int newX = colOffset + shift;
offset++;
// Stored in b-> g-> r order.
rgba.Bgr = Unsafe.As<byte, Bgr24>(ref colors[colorIndex]);
color.PackFromRgba32(rgba);
pixelRow[newX] = color;
}
offset++;
}
}
}
}
@ -285,10 +421,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
const int ScaleG = 4; // 256/64
const int ComponentCount = 2;
TPixel color = default(TPixel);
Rgba32 rgba = new Rgba32(0, 0, 0, 255);
var color = default(TPixel);
var rgba = new Rgba32(0, 0, 0, 255);
using (PixelArea<TPixel> row = new PixelArea<TPixel>(width, ComponentOrder.Xyz))
using (var row = new PixelArea<TPixel>(width, ComponentOrder.Xyz))
{
for (int y = 0; y < height; y++)
{
@ -327,7 +463,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : struct, IPixel<TPixel>
{
int padding = CalculatePadding(width, 3);
using (PixelArea<TPixel> row = new PixelArea<TPixel>(width, ComponentOrder.Zyx, padding))
using (var row = new PixelArea<TPixel>(width, ComponentOrder.Zyx, padding))
{
for (int y = 0; y < height; y++)
{
@ -351,7 +487,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : struct, IPixel<TPixel>
{
int padding = CalculatePadding(width, 4);
using (PixelArea<TPixel> row = new PixelArea<TPixel>(width, ComponentOrder.Zyxw, padding))
using (var row = new PixelArea<TPixel>(width, ComponentOrder.Zyxw, padding))
{
for (int y = 0; y < height; y++)
{
@ -480,4 +616,4 @@ namespace SixLabors.ImageSharp.Formats.Bmp
};
}
}
}
}

21
src/ImageSharp/Formats/Gif/FrameDecodingMode.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Gif
{
/// <summary>
/// Enumerated frame process modes to apply to multi-frame images.
/// </summary>
public enum FrameDecodingMode
{
/// <summary>
/// Decodes all the frames of a multi-frame image.
/// </summary>
All,
/// <summary>
/// Decodes only the first frame of a multi-frame image.
/// </summary>
First
}
}

5
src/ImageSharp/Formats/Gif/GifDecoder.cs

@ -24,6 +24,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
/// <summary>
/// Gets or sets the decoding mode for multi-frame images
/// </summary>
public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All;
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>

61
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -84,6 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
this.TextEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding;
this.IgnoreMetadata = options.IgnoreMetadata;
this.DecodingMode = options.DecodingMode;
this.configuration = configuration ?? Configuration.Default;
}
@ -95,7 +96,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Gets the text encoding
/// </summary>
public Encoding TextEncoding { get; private set; }
public Encoding TextEncoding { get; }
/// <summary>
/// Gets the decoding mode for multi-frame images
/// </summary>
public FrameDecodingMode DecodingMode { get; }
/// <summary>
/// Decodes the stream to the image.
@ -129,6 +135,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
if (nextFlag == GifConstants.ImageLabel)
{
if (this.previousFrame != null && this.DecodingMode == FrameDecodingMode.First)
{
break;
}
this.ReadFrame();
}
else if (nextFlag == GifConstants.ExtensionIntroducer)
@ -238,14 +249,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
throw new ImageFormatException($"Invalid gif colormap size '{this.logicalScreenDescriptor.GlobalColorTableSize}'");
}
/* // No point doing this as the max width/height is always int.Max and that always bigger than the max size of a gif which is stored in a short.
if (this.logicalScreenDescriptor.Width > Image<TPixel>.MaxWidth || this.logicalScreenDescriptor.Height > Image<TPixel>.MaxHeight)
{
throw new ArgumentOutOfRangeException(
$"The input gif '{this.logicalScreenDescriptor.Width}x{this.logicalScreenDescriptor.Height}' is bigger then the max allowed size '{Image<TPixel>.MaxWidth}x{Image<TPixel>.MaxHeight}'");
}
*/
}
/// <summary>
@ -311,10 +314,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
try
{
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
int length = this.globalColorTableLength;
if (imageDescriptor.LocalColorTableFlag)
{
length = imageDescriptor.LocalColorTableSize * 3;
int length = imageDescriptor.LocalColorTableSize * 3;
localColorTable = ArrayPool<byte>.Shared.Rent(length);
this.currentStream.Read(localColorTable, 0, length);
}
@ -322,7 +324,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
indices = ArrayPool<byte>.Shared.Rent(imageDescriptor.Width * imageDescriptor.Height);
this.ReadFrameIndices(imageDescriptor, indices);
this.ReadFrameColors(indices, localColorTable ?? this.globalColorTable, length, imageDescriptor);
this.ReadFrameColors(indices, localColorTable ?? this.globalColorTable, imageDescriptor);
// Skip any remaining blocks
this.Skip(0);
@ -358,18 +360,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
/// <param name="indices">The indexed pixels.</param>
/// <param name="colorTable">The color table containing the available colors.</param>
/// <param name="colorTableLength">The color table length.</param>
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
private unsafe void ReadFrameColors(byte[] indices, byte[] colorTable, int colorTableLength, GifImageDescriptor descriptor)
private void ReadFrameColors(byte[] indices, byte[] colorTable, GifImageDescriptor descriptor)
{
int imageWidth = this.logicalScreenDescriptor.Width;
int imageHeight = this.logicalScreenDescriptor.Height;
ImageFrame<TPixel> previousFrame = null;
ImageFrame<TPixel> prevFrame = null;
ImageFrame<TPixel> currentFrame = null;
ImageFrame<TPixel> image;
ImageFrame<TPixel> imageFrame;
if (this.previousFrame == null)
{
@ -378,23 +379,23 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.SetFrameMetaData(this.image.Frames.RootFrame.MetaData);
image = this.image.Frames.RootFrame;
imageFrame = this.image.Frames.RootFrame;
}
else
{
if (this.graphicsControlExtension != null &&
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
{
previousFrame = this.previousFrame;
prevFrame = this.previousFrame;
}
currentFrame = this.image.Frames.AddFrame(this.previousFrame); // this clones the frame and adds it the collection
currentFrame = this.image.Frames.AddFrame(this.previousFrame); // This clones the frame and adds it the collection
this.SetFrameMetaData(currentFrame.MetaData);
image = currentFrame;
imageFrame = currentFrame;
this.RestoreToBackground(image);
this.RestoreToBackground(imageFrame);
}
int i = 0;
@ -439,9 +440,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
writeY = y;
}
Span<TPixel> rowSpan = image.GetPixelRowSpan(writeY);
Span<TPixel> rowSpan = imageFrame.GetPixelRowSpan(writeY);
Rgba32 rgba = new Rgba32(0, 0, 0, 255);
var rgba = new Rgba32(0, 0, 0, 255);
for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++)
{
@ -463,13 +464,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
}
if (previousFrame != null)
if (prevFrame != null)
{
this.previousFrame = previousFrame;
this.previousFrame = prevFrame;
return;
}
this.previousFrame = currentFrame == null ? this.image.Frames.RootFrame : currentFrame;
this.previousFrame = currentFrame ?? this.image.Frames.RootFrame;
if (this.graphicsControlExtension != null &&
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground)
@ -518,18 +519,18 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Sets the frames metadata.
/// </summary>
/// <param name="metaData">The meta data.</param>
/// <param name="meta">The meta data.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetFrameMetaData(ImageFrameMetaData metaData)
private void SetFrameMetaData(ImageFrameMetaData meta)
{
if (this.graphicsControlExtension != null)
{
if (this.graphicsControlExtension.DelayTime > 0)
{
metaData.FrameDelay = this.graphicsControlExtension.DelayTime;
meta.FrameDelay = this.graphicsControlExtension.DelayTime;
}
metaData.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
meta.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
}
}
}

10
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -154,18 +154,14 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
// Transparent pixels are much more likely to be found at the end of a palette
int index = -1;
var trans = default(Rgba32);
for (int i = quantized.Palette.Length - 1; i >= 0; i--)
{
quantized.Palette[i].ToXyzwBytes(this.buffer, 0);
quantized.Palette[i].ToRgba32(ref trans);
if (this.buffer[3] > 0)
{
continue;
}
else
if (trans.Equals(default(Rgba32)))
{
index = i;
break;
}
}

9
src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs

@ -1,11 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Gif
{
@ -23,5 +19,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Gets the encoding that should be used when reading comments.
/// </summary>
Encoding TextEncoding { get; }
/// <summary>
/// Gets the decoding mode for multi-frame images
/// </summary>
FrameDecodingMode DecodingMode { get; }
}
}

49
src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs

@ -125,6 +125,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary>
private readonly byte[] huffmanBuffer = new byte[179];
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
private readonly bool ignoreMetadata;
/// <summary>
/// The quality, that will be used to encode the image.
/// </summary>
private readonly int quality;
/// <summary>
/// Gets or sets the subsampling method to use.
/// </summary>
private readonly JpegSubsample? subsample;
/// <summary>
/// The accumulated bits to write to the stream.
/// </summary>
@ -150,37 +165,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary>
private Stream outputStream;
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
private bool ignoreMetadata = false;
/// <summary>
/// Gets or sets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min).
/// </summary>
/// <value>The quality of the jpg image from 0 to 100.</value>
private int quality = 0;
/// <summary>
/// Gets or sets the subsampling method to use.
/// </summary>
private JpegSubsample? subsample;
/// <summary>
/// Initializes a new instance of the <see cref="JpegEncoderCore"/> class.
/// </summary>
/// <param name="options">The options</param>
public JpegEncoderCore(IJpegEncoderOptions options)
{
int quality = options.Quality;
if (quality == 0)
{
quality = 75;
}
this.quality = quality;
this.subsample = options.Subsample ?? (quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420);
// System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1.
this.quality = options.Quality.Clamp(1, 100);
this.subsample = options.Subsample ?? (this.quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420);
this.ignoreMetadata = options.IgnoreMetadata;
}
@ -205,17 +198,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.outputStream = stream;
int quality = this.quality.Clamp(1, 100);
// Convert from a quality rating to a scaling factor.
int scale;
if (this.quality < 50)
{
scale = 5000 / quality;
scale = 5000 / this.quality;
}
else
{
scale = 200 - (quality * 2);
scale = 200 - (this.quality * 2);
}
// Initialize the quantization tables.

4
src/ImageSharp/Formats/Jpeg/JpegEncoder.cs

@ -21,13 +21,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Gets or sets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min).
/// </summary>
/// <value>The quality of the jpg image from 0 to 100.</value>
public int Quality { get; set; }
public int Quality { get; set; } = 75;
/// <summary>
/// Gets or sets the subsample ration, that will be used to encode the image.
/// </summary>
/// <value>The subsample ratio of the jpg image.</value>
public JpegSubsample? Subsample { get; set; }
/// <summary>

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

@ -1198,12 +1198,23 @@ namespace SixLabors.ImageSharp.Formats.Png
{
var chunk = new PngChunk();
this.ReadChunkLength(chunk);
if (chunk.Length < 0)
if (chunk.Length == -1)
{
// IEND
return null;
}
if (chunk.Length < 0 || chunk.Length > this.currentStream.Length - this.currentStream.Position)
{
// Not a valid chunk so we skip back all but one of the four bytes we have just read.
// That lets us read one byte at a time until we reach a known chunk.
this.currentStream.Position -= 3;
return chunk;
}
this.ReadChunkType(chunk);
if (chunk.Type == PngChunkTypes.Data)
{
return chunk;

25
src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs

@ -7,6 +7,7 @@ using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Helpers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@ -87,6 +88,30 @@ namespace SixLabors.ImageSharp.Processing.Processors
}
}
/// <inheritdoc/>
protected override void AfterImageApply(Image<TPixel> source, Rectangle sourceRectangle)
{
ExifProfile profile = source.MetaData.ExifProfile;
if (profile == null)
{
return;
}
if (MathF.Abs(this.Angle) < Constants.Epsilon)
{
// No need to do anything so return.
return;
}
profile.RemoveValue(ExifTag.Orientation);
if (this.Expand && profile.GetValue(ExifTag.PixelXDimension) != null)
{
profile.SetValue(ExifTag.PixelXDimension, source.Width);
profile.SetValue(ExifTag.PixelYDimension, source.Height);
}
}
/// <summary>
/// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees.
/// </summary>

123
src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs

@ -23,11 +23,6 @@ namespace SixLabors.ImageSharp.Quantizers
/// </summary>
private readonly Dictionary<TPixel, byte> colorMap = new Dictionary<TPixel, byte>();
/// <summary>
/// The pixel buffer, used to reduce allocations.
/// </summary>
private readonly byte[] pixelBuffer = new byte[4];
/// <summary>
/// Stores the tree
/// </summary>
@ -43,6 +38,11 @@ namespace SixLabors.ImageSharp.Quantizers
/// </summary>
private TPixel[] palette;
/// <summary>
/// The transparent index
/// </summary>
private byte transparentIndex;
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer{TPixel}"/> class.
/// </summary>
@ -61,6 +61,7 @@ namespace SixLabors.ImageSharp.Quantizers
this.colors = (byte)maxColors.Clamp(1, 255);
this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors));
this.palette = null;
this.colorMap.Clear();
return base.Quantize(image, this.colors);
}
@ -72,7 +73,8 @@ namespace SixLabors.ImageSharp.Quantizers
// pass of the algorithm by avoiding transforming rows of identical color.
TPixel sourcePixel = source[0, 0];
TPixel previousPixel = sourcePixel;
byte pixelValue = this.QuantizePixel(sourcePixel);
var rgba = default(Rgba32);
byte pixelValue = this.QuantizePixel(sourcePixel, ref rgba);
TPixel[] colorPalette = this.GetPalette();
TPixel transformedPixel = colorPalette[pixelValue];
@ -91,7 +93,7 @@ namespace SixLabors.ImageSharp.Quantizers
if (!previousPixel.Equals(sourcePixel))
{
// Quantize the pixel
pixelValue = this.QuantizePixel(sourcePixel);
pixelValue = this.QuantizePixel(sourcePixel, ref rgba);
// And setup the previous pointer
previousPixel = sourcePixel;
@ -117,24 +119,57 @@ namespace SixLabors.ImageSharp.Quantizers
protected override void InitialQuantizePixel(TPixel pixel)
{
// Add the color to the Octree
this.octree.AddColor(pixel, this.pixelBuffer);
var rgba = default(Rgba32);
this.octree.AddColor(pixel, ref rgba);
}
/// <inheritdoc/>
protected override TPixel[] GetPalette()
{
return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, (byte)1)));
if (this.palette == null)
{
this.palette = this.octree.Palletize(Math.Max(this.colors, (byte)1));
this.transparentIndex = this.GetTransparentIndex();
}
return this.palette;
}
/// <summary>
/// Returns the index of the first instance of the transparent color in the palette.
/// </summary>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte GetTransparentIndex()
{
// Transparent pixels are much more likely to be found at the end of a palette
int index = this.colors;
var trans = default(Rgba32);
for (int i = this.palette.Length - 1; i >= 0; i--)
{
this.palette[i].ToRgba32(ref trans);
if (trans.Equals(default(Rgba32)))
{
index = i;
}
}
return (byte)index;
}
/// <summary>
/// Process the pixel in the second pass of the algorithm
/// </summary>
/// <param name="pixel">The pixel to quantize</param>
/// <param name="rgba">The color to compare against</param>
/// <returns>
/// The quantized value
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte QuantizePixel(TPixel pixel)
private byte QuantizePixel(TPixel pixel, ref Rgba32 rgba)
{
if (this.Dither)
{
@ -143,13 +178,13 @@ namespace SixLabors.ImageSharp.Quantizers
return this.GetClosestPixel(pixel, this.palette, this.colorMap);
}
pixel.ToXyzwBytes(this.pixelBuffer, 0);
if (this.pixelBuffer[3] == 0)
pixel.ToRgba32(ref rgba);
if (rgba.Equals(default(Rgba32)))
{
return this.colors;
return this.transparentIndex;
}
return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer);
return (byte)this.octree.GetPaletteIndex(pixel, ref rgba);
}
/// <summary>
@ -232,8 +267,8 @@ namespace SixLabors.ImageSharp.Quantizers
/// Add a given color value to the Octree
/// </summary>
/// <param name="pixel">The pixel data.</param>
/// <param name="buffer">The buffer array.</param>
public void AddColor(TPixel pixel, byte[] buffer)
/// <param name="rgba">The color.</param>
public void AddColor(TPixel pixel, ref Rgba32 rgba)
{
// Check if this request is for the same color as the last
if (this.previousColor.Equals(pixel))
@ -243,18 +278,18 @@ namespace SixLabors.ImageSharp.Quantizers
if (this.previousNode == null)
{
this.previousColor = pixel;
this.root.AddColor(pixel, this.maxColorBits, 0, this, buffer);
this.root.AddColor(pixel, this.maxColorBits, 0, this, ref rgba);
}
else
{
// Just update the previous node
this.previousNode.Increment(pixel, buffer);
this.previousNode.Increment(pixel, ref rgba);
}
}
else
{
this.previousColor = pixel;
this.root.AddColor(pixel, this.maxColorBits, 0, this, buffer);
this.root.AddColor(pixel, this.maxColorBits, 0, this, ref rgba);
}
}
@ -286,13 +321,13 @@ namespace SixLabors.ImageSharp.Quantizers
/// Get the palette index for the passed color
/// </summary>
/// <param name="pixel">The pixel data.</param>
/// <param name="buffer">The buffer array.</param>
/// <param name="rgba">The color to map to.</param>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
public int GetPaletteIndex(TPixel pixel, byte[] buffer)
public int GetPaletteIndex(TPixel pixel, ref Rgba32 rgba)
{
return this.root.GetPaletteIndex(pixel, 0, buffer);
return this.root.GetPaletteIndex(pixel, 0, ref rgba);
}
/// <summary>
@ -414,17 +449,17 @@ namespace SixLabors.ImageSharp.Quantizers
/// <summary>
/// Add a color into the tree
/// </summary>
/// <param name="pixel">The color</param>
/// <param name="pixel">The pixel color</param>
/// <param name="colorBits">The number of significant color bits</param>
/// <param name="level">The level in the tree</param>
/// <param name="octree">The tree to which this node belongs</param>
/// <param name="buffer">The buffer array.</param>
public void AddColor(TPixel pixel, int colorBits, int level, Octree octree, byte[] buffer)
/// <param name="rgba">The color to map to.</param>
public void AddColor(TPixel pixel, int colorBits, int level, Octree octree, ref Rgba32 rgba)
{
// Update the color information if this is a leaf
if (this.leaf)
{
this.Increment(pixel, buffer);
this.Increment(pixel, ref rgba);
// Setup the previous node
octree.TrackPrevious(this);
@ -433,11 +468,11 @@ namespace SixLabors.ImageSharp.Quantizers
{
// Go to the next level down in the tree
int shift = 7 - level;
pixel.ToXyzwBytes(buffer, 0);
pixel.ToRgba32(ref rgba);
int index = ((buffer[2] & Mask[level]) >> (shift - 2)) |
((buffer[1] & Mask[level]) >> (shift - 1)) |
((buffer[0] & Mask[level]) >> shift);
int index = ((rgba.B & Mask[level]) >> (shift - 2)) |
((rgba.G & Mask[level]) >> (shift - 1)) |
((rgba.R & Mask[level]) >> shift);
OctreeNode child = this.children[index];
@ -449,7 +484,7 @@ namespace SixLabors.ImageSharp.Quantizers
}
// Add the color to the child node
child.AddColor(pixel, colorBits, level + 1, octree, buffer);
child.AddColor(pixel, colorBits, level + 1, octree, ref rgba);
}
}
@ -523,26 +558,26 @@ namespace SixLabors.ImageSharp.Quantizers
/// </summary>
/// <param name="pixel">The pixel data.</param>
/// <param name="level">The level.</param>
/// <param name="buffer">The buffer array.</param>
/// <param name="rgba">The color to map to.</param>
/// <returns>
/// The <see cref="int"/> representing the index of the pixel in the palette.
/// </returns>
public int GetPaletteIndex(TPixel pixel, int level, byte[] buffer)
public int GetPaletteIndex(TPixel pixel, int level, ref Rgba32 rgba)
{
int index = this.paletteIndex;
if (!this.leaf)
{
int shift = 7 - level;
pixel.ToXyzwBytes(buffer, 0);
pixel.ToRgba32(ref rgba);
int pixelIndex = ((buffer[2] & Mask[level]) >> (shift - 2)) |
((buffer[1] & Mask[level]) >> (shift - 1)) |
((buffer[0] & Mask[level]) >> shift);
int pixelIndex = ((rgba.B & Mask[level]) >> (shift - 2)) |
((rgba.G & Mask[level]) >> (shift - 1)) |
((rgba.R & Mask[level]) >> shift);
if (this.children[pixelIndex] != null)
{
index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1, buffer);
index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1, ref rgba);
}
else
{
@ -557,14 +592,14 @@ namespace SixLabors.ImageSharp.Quantizers
/// Increment the pixel count and add to the color information
/// </summary>
/// <param name="pixel">The pixel to add.</param>
/// <param name="buffer">The buffer array.</param>
public void Increment(TPixel pixel, byte[] buffer)
/// <param name="rgba">The color to map to.</param>
public void Increment(TPixel pixel, ref Rgba32 rgba)
{
pixel.ToXyzwBytes(buffer, 0);
pixel.ToRgba32(ref rgba);
this.pixelCount++;
this.red += buffer[0];
this.green += buffer[1];
this.blue += buffer[2];
this.red += rgba.R;
this.green += rgba.G;
this.blue += rgba.B;
}
}
}

1
src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs

@ -58,6 +58,7 @@ namespace SixLabors.ImageSharp.Quantizers
public override QuantizedImage<TPixel> Quantize(ImageFrame<TPixel> image, int maxColors)
{
Array.Resize(ref this.colors, maxColors.Clamp(1, 255));
this.colorMap.Clear();
return base.Quantize(image, maxColors);
}

4
src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs

@ -40,10 +40,10 @@ namespace SixLabors.ImageSharp.Quantizers.Base
}
/// <inheritdoc />
public bool Dither { get; set; } = true;
public bool Dither { get; set; } = false;
/// <inheritdoc />
public IErrorDiffuser DitherType { get; set; } = new SierraLiteDiffuser();
public IErrorDiffuser DitherType { get; set; } = new FloydSteinbergDiffuser();
/// <inheritdoc/>
public virtual QuantizedImage<TPixel> Quantize(ImageFrame<TPixel> image, int maxColors)

1
src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs

@ -139,6 +139,7 @@ namespace SixLabors.ImageSharp.Quantizers
this.colors = maxColors.Clamp(1, 255);
this.palette = null;
this.colorMap.Clear();
try
{

46
tests/ImageSharp.Tests/Drawing/DrawImageTest.cs

@ -9,6 +9,8 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests
{
using System;
public class DrawImageTest : FileTestBase
{
private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32;
@ -42,5 +44,49 @@ namespace SixLabors.ImageSharp.Tests
image.DebugSave(provider, new { mode });
}
}
[Theory]
[WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32)]
public void ImageShouldHandleNegativeLocation(TestImageProvider<Rgba32> provider)
{
using (Image<Rgba32> background = provider.GetImage())
using (var overlay = new Image<Rgba32>(50, 50))
{
overlay.Mutate(x => x.Fill(Rgba32.Black));
int xy = -25;
Rgba32 backgroundPixel = background[0, 0];
Rgba32 overlayPixel = overlay[Math.Abs(xy) + 1, Math.Abs(xy) + 1];
background.Mutate(x => x.DrawImage(overlay, PixelBlenderMode.Normal, 1F, new Size(overlay.Width, overlay.Height), new Point(xy, xy)));
Assert.Equal(Rgba32.White, backgroundPixel);
Assert.Equal(overlayPixel, background[0, 0]);
background.DebugSave(provider, new[] { "Negative" });
}
}
[Theory]
[WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32)]
public void ImageShouldHandlePositiveLocation(TestImageProvider<Rgba32> provider)
{
using (Image<Rgba32> background = provider.GetImage())
using (var overlay = new Image<Rgba32>(50, 50))
{
overlay.Mutate(x => x.Fill(Rgba32.Black));
int xy = 25;
Rgba32 backgroundPixel = background[xy - 1, xy - 1];
Rgba32 overlayPixel = overlay[0, 0];
background.Mutate(x => x.DrawImage(overlay, PixelBlenderMode.Normal, 1F, new Size(overlay.Width, overlay.Height), new Point(xy, xy)));
Assert.Equal(Rgba32.White, backgroundPixel);
Assert.Equal(overlayPixel, background[xy, xy]);
background.DebugSave(provider, new[] { "Positive" });
}
}
}
}

5
tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests
public class BmpDecoderTests : FileTestBase
{
[Theory]
[WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgb24)]
[WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32)]
public void DecodeBmp<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
@ -42,7 +42,8 @@ namespace SixLabors.ImageSharp.Tests
[InlineData(TestImages.Bmp.Car, 24)]
[InlineData(TestImages.Bmp.F, 24)]
[InlineData(TestImages.Bmp.NegHeight, 24)]
[InlineData(TestImages.Bmp.Bpp8, 8)]
[InlineData(TestImages.Bmp.Bit8, 8)]
[InlineData(TestImages.Bmp.Bit8Inverted, 8)]
public void DetectPixelSize(string imagePath, int expectedPixelSize)
{
TestFile testFile = TestFile.Create(imagePath);

37
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System.Text;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@ -47,12 +46,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void Decode_IgnoreMetadataIsFalse_CommentsAreRead()
{
GifDecoder options = new GifDecoder()
var options = new GifDecoder
{
IgnoreMetadata = false
};
TestFile testFile = TestFile.Create(TestImages.Gif.Rings);
var testFile = TestFile.Create(TestImages.Gif.Rings);
using (Image<Rgba32> image = testFile.CreateImage(options))
{
@ -65,12 +64,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored()
{
GifDecoder options = new GifDecoder()
var options = new GifDecoder
{
IgnoreMetadata = true
};
TestFile testFile = TestFile.Create(TestImages.Gif.Rings);
var testFile = TestFile.Create(TestImages.Gif.Rings);
using (Image<Rgba32> image = testFile.CreateImage(options))
{
@ -81,12 +80,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding()
{
GifDecoder options = new GifDecoder()
var options = new GifDecoder
{
TextEncoding = Encoding.Unicode
};
TestFile testFile = TestFile.Create(TestImages.Gif.Rings);
var testFile = TestFile.Create(TestImages.Gif.Rings);
using (Image<Rgba32> image = testFile.CreateImage(options))
{
@ -95,6 +94,28 @@ namespace SixLabors.ImageSharp.Tests
}
}
[Theory]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)]
public void CanDecodeJustOneFrame<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.First }))
{
Assert.Equal(1, image.Frames.Count);
}
}
[Theory]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)]
public void CanDecodeAllFrames<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.All }))
{
Assert.True(image.Frames.Count > 1);
}
}
[Theory]
[InlineData(TestImages.Gif.Cheers, 8)]
[InlineData(TestImages.Gif.Giphy, 8)]
@ -109,4 +130,4 @@ namespace SixLabors.ImageSharp.Tests
}
}
}
}
}

54
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void LoadResizeSave<TPixel>(TestImageProvider<TPixel> provider, int quality, JpegSubsample subsample)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(x=>x.Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max })))
using (Image<TPixel> image = provider.GetImage(x => x.Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max })))
{
image.MetaData.ExifProfile = null; // Reduce the size of the file
@ -62,8 +62,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
image.Save(outputStream, new JpegEncoder()
{
Subsample = subSample,
Quality = quality
Subsample = subSample,
Quality = quality
});
}
}
@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
using (MemoryStream memStream = new MemoryStream())
{
input.Save(memStream, options);
input.Save(memStream, options);
memStream.Position = 0;
using (Image<Rgba32> output = Image.Load<Rgba32>(memStream))
@ -118,5 +118,51 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
}
}
[Fact]
public void Encode_Quality_0_And_1_Are_Identical()
{
var options = new JpegEncoder
{
Quality = 0
};
var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora);
using (Image<Rgba32> input = testFile.CreateImage())
using (var memStream0 = new MemoryStream())
using (var memStream1 = new MemoryStream())
{
input.SaveAsJpeg(memStream0, options);
options.Quality = 1;
input.SaveAsJpeg(memStream1, options);
Assert.Equal(memStream0.ToArray(), memStream1.ToArray());
}
}
[Fact]
public void Encode_Quality_0_And_100_Are_Not_Identical()
{
var options = new JpegEncoder
{
Quality = 0
};
var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora);
using (Image<Rgba32> input = testFile.CreateImage())
using (var memStream0 = new MemoryStream())
using (var memStream1 = new MemoryStream())
{
input.SaveAsJpeg(memStream0, options);
options.Quality = 100;
input.SaveAsJpeg(memStream1, options);
Assert.NotEqual(memStream0.ToArray(), memStream1.ToArray());
}
}
}
}

1
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -24,6 +24,7 @@ namespace SixLabors.ImageSharp.Tests
TestImages.Png.Splash, TestImages.Png.Indexed,
TestImages.Png.FilterVar,
TestImages.Png.Bad.ChunkLength1,
TestImages.Png.Bad.CorruptedChunk,
TestImages.Png.VimImage1,
TestImages.Png.VersioningImage1,

7
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -17,10 +17,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CoreCompat.System.Drawing" Version="1.0.0-beta006" />
<PackageReference Include="xunit" Version="2.3.0-beta4-build3742" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0-beta4-build3742" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
<PackageReference Include="Moq" Version="4.7.99" />
<PackageReference Include="xunit" Version="2.3.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0" />
<PackageReference Include="Moq" Version="4.7.137" />
<!--<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta001">
<PrivateAssets>All</PrivateAssets>
</PackageReference>-->

95
tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs

@ -0,0 +1,95 @@
namespace SixLabors.ImageSharp.Tests
{
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Quantizers;
using Xunit;
public class QuantizedImageTests
{
[Theory]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)]
public void PaletteQuantizerYieldsCorrectTransparentPixel<TPixel>(TestImageProvider<TPixel> provider, bool dither)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
Assert.True(image[0, 0].Equals(default(TPixel)));
IQuantizer<TPixel> quantizer = new PaletteQuantizer<TPixel> { Dither = dither };
foreach (ImageFrame<TPixel> frame in image.Frames)
{
QuantizedImage<TPixel> quantized = quantizer.Quantize(frame, 256);
int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.Pixels[0]);
}
}
}
[Theory]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)]
public void OctreeQuantizerYieldsCorrectTransparentPixel<TPixel>(TestImageProvider<TPixel> provider, bool dither)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
Assert.True(image[0, 0].Equals(default(TPixel)));
IQuantizer<TPixel> quantizer = new OctreeQuantizer<TPixel> { Dither = dither };
foreach (ImageFrame<TPixel> frame in image.Frames)
{
QuantizedImage<TPixel> quantized = quantizer.Quantize(frame, 256);
int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.Pixels[0]);
}
}
}
[Theory]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)]
public void WuQuantizerYieldsCorrectTransparentPixel<TPixel>(TestImageProvider<TPixel> provider, bool dither)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
Assert.True(image[0, 0].Equals(default(TPixel)));
IQuantizer<TPixel> quantizer = new WuQuantizer<TPixel> { Dither = dither };
foreach (ImageFrame<TPixel> frame in image.Frames)
{
QuantizedImage<TPixel> quantized = quantizer.Quantize(frame, 256);
int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.Pixels[0]);
}
}
}
private int GetTransparentIndex<TPixel>(QuantizedImage<TPixel> quantized)
where TPixel : struct, IPixel<TPixel>
{
// Transparent pixels are much more likely to be found at the end of a palette
int index = -1;
var trans = default(Rgba32);
for (int i = quantized.Palette.Length - 1; i >= 0; i--)
{
quantized.Palette[i].ToRgba32(ref trans);
if (trans.Equals(default(Rgba32)))
{
index = i;
}
}
return index;
}
}
}

12
tests/ImageSharp.Tests/TestImages.cs

@ -59,6 +59,7 @@ namespace SixLabors.ImageSharp.Tests
// Odd chunk lengths
public const string ChunkLength1 = "Png/chunklength1.png";
public const string ChunkLength2 = "Png/chunklength2.png";
public const string CorruptedChunk = "Png/big-corrupted-chunk.png";
}
public static readonly string[] All =
@ -131,14 +132,19 @@ namespace SixLabors.ImageSharp.Tests
public static class Bmp
{
// Note: The inverted images have been generated by altering the BitmapInfoHeader using a hex editor.
// As such, the expected pixel output will be the reverse of the unaltered equivalent images.
public const string Car = "Bmp/Car.bmp";
public const string F = "Bmp/F.bmp";
public const string Bpp8 = "Bmp/bpp8.bmp";
public const string NegHeight = "Bmp/neg_height.bmp";
public const string CoreHeader = "Bmp/BitmapCoreHeaderQR.bmp";
public const string V5Header = "Bmp/BITMAPV5HEADER.bmp";
public static readonly string[] All = { Car, F, NegHeight, CoreHeader, V5Header, Bpp8 };
public const string RLE = "Bmp/RunLengthEncoded.bmp";
public const string RLEInverted = "Bmp/RunLengthEncoded-inverted.bmp";
public const string Bit8 = "Bmp/test8.bmp";
public const string Bit8Inverted = "Bmp/test8-inverted.bmp";
public static readonly string[] All = { Car, F, NegHeight, CoreHeader, V5Header, RLE, RLEInverted, Bit8, Bit8Inverted };
}
public static class Gif

BIN
tests/Images/Input/Bmp/RunLengthEncoded-inverted.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
tests/Images/Input/Bmp/RunLengthEncoded.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
tests/Images/Input/Bmp/bpp8.bmp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

BIN
tests/Images/Input/Bmp/test8-inverted.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
tests/Images/Input/Bmp/test8.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
tests/Images/Input/Png/big-corrupted-chunk.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Loading…
Cancel
Save