Browse Source

Merge branch 'master' into wip-323

af/merge-core
James Jackson-South 8 years ago
committed by GitHub
parent
commit
756f94ca5c
  1. 14
      src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs
  2. 21
      src/ImageSharp/Formats/Gif/FrameDecodingMode.cs
  3. 5
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  4. 61
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  5. 10
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  6. 9
      src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
  7. 122
      src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
  8. 2
      src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs
  9. 46
      tests/ImageSharp.Tests/Drawing/DrawImageTest.cs
  10. 37
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  11. 95
      tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs

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);
});
}

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; }
}
}

122
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>
@ -73,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];
@ -92,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;
@ -118,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)
{
@ -144,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>
@ -233,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))
@ -244,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);
}
}
@ -287,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>
@ -415,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);
@ -434,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];
@ -450,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);
}
}
@ -524,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
{
@ -558,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;
}
}
}

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

@ -40,7 +40,7 @@ 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 FloydSteinbergDiffuser();

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" });
}
}
}
}

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;
@ -45,12 +44,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))
{
@ -63,12 +62,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))
{
@ -79,12 +78,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))
{
@ -92,5 +91,27 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal("浉条卥慨灲", image.MetaData.Properties[0].Value);
}
}
[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);
}
}
}
}
}

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;
}
}
}
Loading…
Cancel
Save