diff --git a/README.md b/README.md
index 03ca0653f..d9d2628bb 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,19 @@
-
-[](https://raw.githubusercontent.com/SixLabors/ImageSharp/master/APACHE-2.0-LICENSE.txt)
-[](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-[](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)
-[](#backers)
-[](#sponsors)
-
-#
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.
+
+
+
+ ImageSharp
+
+
+
+
+
+
+
+
+
+### **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
diff --git a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs
index 213ab1b4a..47763c0aa 100644
--- a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs
+++ b/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
///
/// Gets the image to blend.
///
- public Image Image { get; private set; }
+ public Image Image { get; }
///
/// 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 amount = new Buffer(width))
- using (PixelAccessor toBlendPixels = targetImage.Lock())
- using (PixelAccessor sourcePixels = source.Lock())
+ using (var amount = new Buffer(width))
{
for (int i = 0; i < width; i++)
{
@@ -99,8 +97,8 @@ namespace SixLabors.ImageSharp.Drawing.Processors
configuration.ParallelOptions,
y =>
{
- Span background = sourcePixels.GetRowSpan(y).Slice(minX, width);
- Span foreground = toBlendPixels.GetRowSpan(y - this.Location.Y).Slice(0, width);
+ Span background = source.GetPixelRowSpan(y).Slice(minX, width);
+ Span foreground = targetImage.GetPixelRowSpan(y - this.Location.Y).Slice(targetX, width);
this.blender.Blend(background, background, foreground, amount);
});
}
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 625b2a3a8..39bab442f 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -29,6 +29,26 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
private const int Rgb16BMask = 0x0000001F;
+ ///
+ /// RLE8 flag value that indicates following byte has special meaning
+ ///
+ private const int RleCommand = 0x00;
+
+ ///
+ /// RLE8 flag value marking end of a scan line
+ ///
+ private const int RleEndOfLine = 0x00;
+
+ ///
+ /// RLE8 flag value marking end of bitmap data
+ ///
+ private const int RleEndOfBitmap = 0x01;
+
+ ///
+ /// RLE8 flag value marking the start of [x,y] offset instruction
+ ///
+ private const int RleDelta = 0x02;
+
///
/// The stream to decode from.
///
@@ -128,7 +148,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
+ $"bigger then the max allowed size '{int.MaxValue}x{int.MaxValue}'");
}
- Image image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height);
+ var image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height);
using (PixelAccessor 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
/// The height of the bitmap.
/// Whether the bitmap is inverted.
/// The representing the inverted value.
+ [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;
}
+ ///
+ /// Looks up color values and builds the image from de-compressed RLE8 data.
+ /// Compresssed RLE8 stream is uncompressed by
+ ///
+ /// The pixel format.
+ /// The to assign the palette to.
+ /// The containing the colors.
+ /// The width of the bitmap.
+ /// The height of the bitmap.
+ /// Whether the bitmap is inverted.
+ private void ReadRle8(PixelAccessor pixels, byte[] colors, int width, int height, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ var color = default(TPixel);
+ var rgba = new Rgba32(0, 0, 0, 255);
+
+ using (var buffer = Buffer2D.CreateClean(width, height))
+ {
+ this.UncompressRle8(width, buffer);
+
+ for (int y = 0; y < height; y++)
+ {
+ int newY = Invert(y, height, inverted);
+ Span bufferRow = buffer.GetRowSpan(y);
+ Span pixelRow = pixels.GetRowSpan(newY);
+
+ for (int x = 0; x < width; x++)
+ {
+ rgba.Bgr = Unsafe.As(ref colors[bufferRow[x] * 4]);
+ color.PackFromRgba32(rgba);
+ pixelRow[x] = color;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Produce uncompressed bitmap data from RLE8 stream
+ ///
+ ///
+ /// RLE8 is a 2-byte run-length encoding
+ ///
If first byte is 0, the second byte may have special meaning
+ ///
Otherwise, first byte is the length of the run and second byte is the color for the run
+ ///
+ /// The width of the bitmap.
+ /// Buffer for uncompressed data.
+ private void UncompressRle8(int w, Span 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];
+ }
+ }
+ }
+ }
+
///
/// Reads the color palette from the stream.
///
@@ -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.CreateClean(arrayWidth + padding))
{
- int newY = Invert(y, height, inverted);
- this.currentStream.Read(row, 0, row.Length);
- int offset = 0;
- Span 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 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(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(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 row = new PixelArea(width, ComponentOrder.Xyz))
+ using (var row = new PixelArea(width, ComponentOrder.Xyz))
{
for (int y = 0; y < height; y++)
{
@@ -327,7 +463,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : struct, IPixel
{
int padding = CalculatePadding(width, 3);
- using (PixelArea row = new PixelArea(width, ComponentOrder.Zyx, padding))
+ using (var row = new PixelArea(width, ComponentOrder.Zyx, padding))
{
for (int y = 0; y < height; y++)
{
@@ -351,7 +487,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : struct, IPixel
{
int padding = CalculatePadding(width, 4);
- using (PixelArea row = new PixelArea(width, ComponentOrder.Zyxw, padding))
+ using (var row = new PixelArea(width, ComponentOrder.Zyxw, padding))
{
for (int y = 0; y < height; y++)
{
@@ -480,4 +616,4 @@ namespace SixLabors.ImageSharp.Formats.Bmp
};
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Gif/FrameDecodingMode.cs b/src/ImageSharp/Formats/Gif/FrameDecodingMode.cs
new file mode 100644
index 000000000..05791c92e
--- /dev/null
+++ b/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
+{
+ ///
+ /// Enumerated frame process modes to apply to multi-frame images.
+ ///
+ public enum FrameDecodingMode
+ {
+ ///
+ /// Decodes all the frames of a multi-frame image.
+ ///
+ All,
+
+ ///
+ /// Decodes only the first frame of a multi-frame image.
+ ///
+ First
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs
index ca3232cd2..20bebb1d3 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoder.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs
@@ -24,6 +24,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
+ ///
+ /// Gets or sets the decoding mode for multi-frame images
+ ///
+ public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All;
+
///
public Image Decode(Configuration configuration, Stream stream)
where TPixel : struct, IPixel
diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index bfd441259..453197b0c 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/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
///
/// Gets the text encoding
///
- public Encoding TextEncoding { get; private set; }
+ public Encoding TextEncoding { get; }
+
+ ///
+ /// Gets the decoding mode for multi-frame images
+ ///
+ public FrameDecodingMode DecodingMode { get; }
///
/// 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.MaxWidth || this.logicalScreenDescriptor.Height > Image.MaxHeight)
- {
- throw new ArgumentOutOfRangeException(
- $"The input gif '{this.logicalScreenDescriptor.Width}x{this.logicalScreenDescriptor.Height}' is bigger then the max allowed size '{Image.MaxWidth}x{Image.MaxHeight}'");
- }
- */
}
///
@@ -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.Shared.Rent(length);
this.currentStream.Read(localColorTable, 0, length);
}
@@ -322,7 +324,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
indices = ArrayPool.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
///
/// The indexed pixels.
/// The color table containing the available colors.
- /// The color table length.
/// The
- 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 previousFrame = null;
+ ImageFrame prevFrame = null;
ImageFrame currentFrame = null;
- ImageFrame image;
+ ImageFrame 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 rowSpan = image.GetPixelRowSpan(writeY);
+ Span 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
///
/// Sets the frames metadata.
///
- /// The meta data.
+ /// The meta data.
[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;
}
}
}
diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index b10f8a2e0..1b145a79e 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/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;
}
}
diff --git a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
index 60c39f936..a2288f30a 100644
--- a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
+++ b/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.
///
Encoding TextEncoding { get; }
+
+ ///
+ /// Gets the decoding mode for multi-frame images
+ ///
+ FrameDecodingMode DecodingMode { get; }
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs
index 2deb3f62d..2912a8719 100644
--- a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs
@@ -125,6 +125,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
///
private readonly byte[] huffmanBuffer = new byte[179];
+ ///
+ /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
+ ///
+ private readonly bool ignoreMetadata;
+
+ ///
+ /// The quality, that will be used to encode the image.
+ ///
+ private readonly int quality;
+
+ ///
+ /// Gets or sets the subsampling method to use.
+ ///
+ private readonly JpegSubsample? subsample;
+
///
/// The accumulated bits to write to the stream.
///
@@ -150,37 +165,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
///
private Stream outputStream;
- ///
- /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
- ///
- private bool ignoreMetadata = false;
-
- ///
- /// 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).
- ///
- /// The quality of the jpg image from 0 to 100.
- private int quality = 0;
-
- ///
- /// Gets or sets the subsampling method to use.
- ///
- private JpegSubsample? subsample;
-
///
/// Initializes a new instance of the class.
///
/// The options
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.
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
index 3d79faabc..8850f581c 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
+++ b/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).
///
- /// The quality of the jpg image from 0 to 100.
- public int Quality { get; set; }
+ public int Quality { get; set; } = 75;
///
/// Gets or sets the subsample ration, that will be used to encode the image.
///
- /// The subsample ratio of the jpg image.
public JpegSubsample? Subsample { get; set; }
///
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index c9a5b6b1f..52b571761 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/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;
diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
index a7fb400ac..86a0c7360 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
+++ b/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
}
}
+ ///
+ protected override void AfterImageApply(Image 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);
+ }
+ }
+
///
/// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees.
///
diff --git a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
index 49adce23b..d646a680e 100644
--- a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
+++ b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
@@ -23,11 +23,6 @@ namespace SixLabors.ImageSharp.Quantizers
///
private readonly Dictionary colorMap = new Dictionary();
- ///
- /// The pixel buffer, used to reduce allocations.
- ///
- private readonly byte[] pixelBuffer = new byte[4];
-
///
/// Stores the tree
///
@@ -43,6 +38,11 @@ namespace SixLabors.ImageSharp.Quantizers
///
private TPixel[] palette;
+ ///
+ /// The transparent index
+ ///
+ private byte transparentIndex;
+
///
/// Initializes a new instance of the class.
///
@@ -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);
}
///
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;
+ }
+
+ ///
+ /// Returns the index of the first instance of the transparent color in the palette.
+ ///
+ ///
+ /// The .
+ ///
+ [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;
}
///
/// Process the pixel in the second pass of the algorithm
///
/// The pixel to quantize
+ /// The color to compare against
///
/// The quantized value
///
[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);
}
///
@@ -232,8 +267,8 @@ namespace SixLabors.ImageSharp.Quantizers
/// Add a given color value to the Octree
///
/// The pixel data.
- /// The buffer array.
- public void AddColor(TPixel pixel, byte[] buffer)
+ /// The color.
+ 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
///
/// The pixel data.
- /// The buffer array.
+ /// The color to map to.
///
/// The .
///
- 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);
}
///
@@ -414,17 +449,17 @@ namespace SixLabors.ImageSharp.Quantizers
///
/// Add a color into the tree
///
- /// The color
+ /// The pixel color
/// The number of significant color bits
/// The level in the tree
/// The tree to which this node belongs
- /// The buffer array.
- public void AddColor(TPixel pixel, int colorBits, int level, Octree octree, byte[] buffer)
+ /// The color to map to.
+ 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
///
/// The pixel data.
/// The level.
- /// The buffer array.
+ /// The color to map to.
///
/// The representing the index of the pixel in the palette.
///
- 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
///
/// The pixel to add.
- /// The buffer array.
- public void Increment(TPixel pixel, byte[] buffer)
+ /// The color to map to.
+ 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;
}
}
}
diff --git a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs
index ca11a042f..0b95c09a6 100644
--- a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs
+++ b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs
@@ -58,6 +58,7 @@ namespace SixLabors.ImageSharp.Quantizers
public override QuantizedImage Quantize(ImageFrame image, int maxColors)
{
Array.Resize(ref this.colors, maxColors.Clamp(1, 255));
+ this.colorMap.Clear();
return base.Quantize(image, maxColors);
}
diff --git a/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs b/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs
index 7f58ff1bf..d57865c97 100644
--- a/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs
+++ b/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs
@@ -40,10 +40,10 @@ namespace SixLabors.ImageSharp.Quantizers.Base
}
///
- public bool Dither { get; set; } = true;
+ public bool Dither { get; set; } = false;
///
- public IErrorDiffuser DitherType { get; set; } = new SierraLiteDiffuser();
+ public IErrorDiffuser DitherType { get; set; } = new FloydSteinbergDiffuser();
///
public virtual QuantizedImage Quantize(ImageFrame image, int maxColors)
diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs
index 77c421468..8ab390f4e 100644
--- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs
+++ b/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
{
diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs
index 94dd903b4..2e3a730fc 100644
--- a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs
+++ b/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 provider)
+ {
+ using (Image background = provider.GetImage())
+ using (var overlay = new Image(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 provider)
+ {
+ using (Image background = provider.GetImage())
+ using (var overlay = new Image(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" });
+ }
+ }
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
index b6f894386..17a559a94 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
+++ b/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(TestImageProvider provider)
where TPixel : struct, IPixel
{
@@ -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);
diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
index a13730eb6..e8661a715 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
+++ b/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 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 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 image = testFile.CreateImage(options))
{
@@ -95,6 +94,28 @@ namespace SixLabors.ImageSharp.Tests
}
}
+ [Theory]
+ [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)]
+ public void CanDecodeJustOneFrame(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.First }))
+ {
+ Assert.Equal(1, image.Frames.Count);
+ }
+ }
+
+ [Theory]
+ [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)]
+ public void CanDecodeAllFrames(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image 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
}
}
}
-}
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
index 3bd1ed265..c8d416bea 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
@@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void LoadResizeSave(TestImageProvider provider, int quality, JpegSubsample subsample)
where TPixel : struct, IPixel
{
- using (Image image = provider.GetImage(x=>x.Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max })))
+ using (Image 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 output = Image.Load(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 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 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());
+ }
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
index 41102f064..b2b4b2f03 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
+++ b/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,
diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
index e8a6e8c59..2f45e4c83 100644
--- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
+++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
@@ -17,10 +17,9 @@
-
-
-
-
+
+
+
diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs
new file mode 100644
index 000000000..a0b14b09b
--- /dev/null
+++ b/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(TestImageProvider provider, bool dither)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage())
+ {
+ Assert.True(image[0, 0].Equals(default(TPixel)));
+
+ IQuantizer quantizer = new PaletteQuantizer { Dither = dither };
+
+ foreach (ImageFrame frame in image.Frames)
+ {
+ QuantizedImage 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(TestImageProvider provider, bool dither)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage())
+ {
+ Assert.True(image[0, 0].Equals(default(TPixel)));
+
+ IQuantizer quantizer = new OctreeQuantizer { Dither = dither };
+
+ foreach (ImageFrame frame in image.Frames)
+ {
+ QuantizedImage 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(TestImageProvider provider, bool dither)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage())
+ {
+ Assert.True(image[0, 0].Equals(default(TPixel)));
+
+ IQuantizer quantizer = new WuQuantizer { Dither = dither };
+
+ foreach (ImageFrame frame in image.Frames)
+ {
+ QuantizedImage quantized = quantizer.Quantize(frame, 256);
+
+ int index = this.GetTransparentIndex(quantized);
+ Assert.Equal(index, quantized.Pixels[0]);
+ }
+ }
+ }
+
+ private int GetTransparentIndex(QuantizedImage quantized)
+ where TPixel : struct, IPixel
+ {
+ // 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 1284f4aec..e1ff70d4c 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/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
diff --git a/tests/Images/Input/Bmp/RunLengthEncoded-inverted.bmp b/tests/Images/Input/Bmp/RunLengthEncoded-inverted.bmp
new file mode 100644
index 000000000..bce0be02f
Binary files /dev/null and b/tests/Images/Input/Bmp/RunLengthEncoded-inverted.bmp differ
diff --git a/tests/Images/Input/Bmp/RunLengthEncoded.bmp b/tests/Images/Input/Bmp/RunLengthEncoded.bmp
new file mode 100644
index 000000000..4c2397988
Binary files /dev/null and b/tests/Images/Input/Bmp/RunLengthEncoded.bmp differ
diff --git a/tests/Images/Input/Bmp/bpp8.bmp b/tests/Images/Input/Bmp/bpp8.bmp
deleted file mode 100644
index 8ea6f4acd..000000000
Binary files a/tests/Images/Input/Bmp/bpp8.bmp and /dev/null differ
diff --git a/tests/Images/Input/Bmp/test8-inverted.bmp b/tests/Images/Input/Bmp/test8-inverted.bmp
new file mode 100644
index 000000000..b0909ae6e
Binary files /dev/null and b/tests/Images/Input/Bmp/test8-inverted.bmp differ
diff --git a/tests/Images/Input/Bmp/test8.bmp b/tests/Images/Input/Bmp/test8.bmp
new file mode 100644
index 000000000..3be9a2066
Binary files /dev/null and b/tests/Images/Input/Bmp/test8.bmp differ
diff --git a/tests/Images/Input/Png/big-corrupted-chunk.png b/tests/Images/Input/Png/big-corrupted-chunk.png
new file mode 100644
index 000000000..830268977
Binary files /dev/null and b/tests/Images/Input/Png/big-corrupted-chunk.png differ