Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
60008fc
use only netcoreapp3.1
antonfirsov Jan 26, 2020
c69eb2b
Simplify API
JimBobSquarePants Feb 28, 2020
e1d662a
Merge branch 'master' into js/dither-quantize-updates
JimBobSquarePants Feb 28, 2020
9d73689
Update IPaletteDitherImageProcessor{TPixel}.cs
JimBobSquarePants Feb 28, 2020
d113c78
Dump progress so far
JimBobSquarePants Feb 29, 2020
cc02140
Merge branch 'master' into js/dither-quantize-updates
JimBobSquarePants Feb 29, 2020
8952954
Better performance
JimBobSquarePants Mar 1, 2020
63f277b
Simplify, fix color mapping, and refactor for performance.
JimBobSquarePants Mar 3, 2020
d9596ef
Cleanup and update references.
JimBobSquarePants Mar 3, 2020
8732717
Merge branch 'af/fast-dev-hack' into js/dither-quantize-updates
antonfirsov Mar 3, 2020
590777c
extend EncodeGif benchmark
antonfirsov Mar 3, 2020
f0d55ca
Revert "Merge branch 'af/fast-dev-hack' into js/dither-quantize-updates"
antonfirsov Mar 4, 2020
161b753
Merge branch 'master' into js/dither-quantize-updates
JimBobSquarePants Mar 4, 2020
1837f6a
Clean up quantized frame API
JimBobSquarePants Mar 5, 2020
89c5fe2
Introduce palette property
JimBobSquarePants Mar 5, 2020
01efb09
Faster png palette encoding.
JimBobSquarePants Mar 5, 2020
fb26d0e
Faster Gif transparency lookup.
JimBobSquarePants Mar 5, 2020
5ca7408
Cleanup
JimBobSquarePants Mar 5, 2020
834ebdf
Merge branch 'master' into js/dither-quantize-updates
JimBobSquarePants Mar 6, 2020
dd32cdf
Remove stackalloc
JimBobSquarePants Mar 6, 2020
71e5a6d
Merge branch 'master' into js/dither-quantize-updates
JimBobSquarePants Mar 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,8 @@ private void Write8Bit<TPixel>(Stream stream, ImageFrame<TPixel> image)
private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
where TPixel : unmanaged, IPixel<TPixel>
{
using IFrameQuantizer<TPixel> quantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration);
using QuantizedFrame<TPixel> quantized = quantizer.QuantizeFrame(image, image.Bounds());
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image, image.Bounds());

ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span;
var color = default(Rgba32);
Expand All @@ -360,7 +360,7 @@ private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Spa

for (int y = image.Height - 1; y >= 0; y--)
{
ReadOnlySpan<byte> pixelSpan = quantized.GetRowSpan(y);
ReadOnlySpan<byte> pixelSpan = quantized.GetPixelRowSpan(y);
stream.Write(pixelSpan);

for (int i = 0; i < this.padding; i++)
Expand Down
3 changes: 2 additions & 1 deletion src/ImageSharp/Formats/Gif/GifEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.IO;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;

namespace SixLabors.ImageSharp.Formats.Gif
Expand All @@ -17,7 +18,7 @@ public sealed class GifEncoder : IImageEncoder, IGifEncoderOptions
/// Gets or sets the quantizer for reducing the color count.
/// Defaults to the <see cref="OctreeQuantizer"/>
/// </summary>
public IQuantizer Quantizer { get; set; } = new OctreeQuantizer();
public IQuantizer Quantizer { get; set; } = KnownQuantizers.Octree;

/// <summary>
/// Gets or sets the color table mode: Global or local.
Expand Down
110 changes: 57 additions & 53 deletions src/ImageSharp/Formats/Gif/GifEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,14 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global;

// Quantize the image returning a palette.
QuantizedFrame<TPixel> quantized;
IndexedImageFrame<TPixel> quantized;
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration))
{
quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
}

// Get the number of bits.
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length);

// Write the header.
this.WriteHeader(stream);
Expand Down Expand Up @@ -119,15 +119,20 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
}

// Clean up.
quantized?.Dispose();
quantized.Dispose();

// TODO: Write extension etc
stream.WriteByte(GifConstants.EndIntroducer);
}

private void EncodeGlobal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> quantized, int transparencyIndex, Stream stream)
private void EncodeGlobal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, int transparencyIndex, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
// The palette quantizer can reuse the same pixel map across multiple frames
// since the palette is unchanging. This allows a reduction of memory usage across
// multi frame gifs using a global palette.
EuclideanPixelMap<TPixel> pixelMap = default;
bool pixelMapSet = false;
for (int i = 0; i < image.Frames.Count; i++)
{
ImageFrame<TPixel> frame = image.Frames[i];
Expand All @@ -142,22 +147,27 @@ private void EncodeGlobal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> qu
}
else
{
using (var paletteFrameQuantizer = new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Options, quantized.Palette))
using (QuantizedFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()))
if (!pixelMapSet)
{
this.WriteImageData(paletteQuantized, stream);
pixelMapSet = true;
pixelMap = new EuclideanPixelMap<TPixel>(this.configuration, quantized.Palette);
}

using var paletteFrameQuantizer = new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap);
using IndexedImageFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds());
this.WriteImageData(paletteQuantized, stream);
}
}
}

private void EncodeLocal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> quantized, Stream stream)
private void EncodeLocal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
ImageFrame<TPixel> previousFrame = null;
GifFrameMetadata previousMeta = null;
foreach (ImageFrame<TPixel> frame in image.Frames)
for (int i = 0; i < image.Frames.Count; i++)
{
ImageFrame<TPixel> frame = image.Frames[i];
ImageFrameMetadata metadata = frame.Metadata;
GifFrameMetadata frameMetadata = metadata.GetGifMetadata();
if (quantized is null)
Expand All @@ -173,27 +183,23 @@ private void EncodeLocal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> qua
MaxColors = frameMetadata.ColorTableLength
};

using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, options))
{
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
}
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, options);
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
}
else
{
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration))
{
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
}
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration);
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
}
}

this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length);
this.WriteGraphicalControlExtension(frameMetadata, this.GetTransparentIndex(quantized), stream);
this.WriteImageDescriptor(frame, true, stream);
this.WriteColorTable(quantized, stream);
this.WriteImageData(quantized, stream);

quantized?.Dispose();
quantized.Dispose();
quantized = null; // So next frame can regenerate it
previousFrame = frame;
previousMeta = frameMetadata;
Expand All @@ -208,25 +214,23 @@ private void EncodeLocal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> qua
/// <returns>
/// The <see cref="int"/>.
/// </returns>
private int GetTransparentIndex<TPixel>(QuantizedFrame<TPixel> quantized)
private int GetTransparentIndex<TPixel>(IndexedImageFrame<TPixel> quantized)
where TPixel : unmanaged, IPixel<TPixel>
{
// Transparent pixels are much more likely to be found at the end of a palette
// Transparent pixels are much more likely to be found at the end of a palette.
int index = -1;
int length = quantized.Palette.Length;
ReadOnlySpan<TPixel> paletteSpan = quantized.Palette.Span;

using (IMemoryOwner<Rgba32> rgbaBuffer = this.memoryAllocator.Allocate<Rgba32>(length))
{
Span<Rgba32> rgbaSpan = rgbaBuffer.GetSpan();
ref Rgba32 paletteRef = ref MemoryMarshal.GetReference(rgbaSpan);
PixelOperations<TPixel>.Instance.ToRgba32(this.configuration, quantized.Palette.Span, rgbaSpan);
using IMemoryOwner<Rgba32> rgbaOwner = quantized.Configuration.MemoryAllocator.Allocate<Rgba32>(paletteSpan.Length);
Span<Rgba32> rgbaSpan = rgbaOwner.GetSpan();
PixelOperations<TPixel>.Instance.ToRgba32(quantized.Configuration, paletteSpan, rgbaSpan);
ref Rgba32 rgbaSpanRef = ref MemoryMarshal.GetReference(rgbaSpan);

for (int i = quantized.Palette.Length - 1; i >= 0; i--)
for (int i = rgbaSpan.Length - 1; i >= 0; i--)
{
if (Unsafe.Add(ref rgbaSpanRef, i).Equals(default))
{
if (Unsafe.Add(ref paletteRef, i).Equals(default))
{
index = i;
}
index = i;
}
}

Expand Down Expand Up @@ -326,16 +330,19 @@ private void WriteComments(GifMetadata metadata, Stream stream)
return;
}

foreach (string comment in metadata.Comments)
for (var i = 0; i < metadata.Comments.Count; i++)
{
string comment = metadata.Comments[i];
this.buffer[0] = GifConstants.ExtensionIntroducer;
this.buffer[1] = GifConstants.CommentLabel;
stream.Write(this.buffer, 0, 2);

// Comment will be stored in chunks of 255 bytes, if it exceeds this size.
ReadOnlySpan<char> commentSpan = comment.AsSpan();
int idx = 0;
for (; idx <= comment.Length - GifConstants.MaxCommentSubBlockLength; idx += GifConstants.MaxCommentSubBlockLength)
for (;
idx <= comment.Length - GifConstants.MaxCommentSubBlockLength;
idx += GifConstants.MaxCommentSubBlockLength)
{
WriteCommentSubBlock(stream, commentSpan, idx, GifConstants.MaxCommentSubBlockLength);
}
Expand Down Expand Up @@ -391,7 +398,8 @@ private void WriteGraphicalControlExtension(GifFrameMetadata metadata, int trans
/// </summary>
/// <param name="extension">The extension to write to the stream.</param>
/// <param name="stream">The stream to write to.</param>
public void WriteExtension(IGifExtension extension, Stream stream)
private void WriteExtension<TGifExtension>(TGifExtension extension, Stream stream)
where TGifExtension : struct, IGifExtension
{
this.buffer[0] = GifConstants.ExtensionIntroducer;
this.buffer[1] = extension.Label;
Expand Down Expand Up @@ -437,37 +445,33 @@ private void WriteImageDescriptor<TPixel>(ImageFrame<TPixel> image, bool hasColo
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode.</param>
/// <param name="stream">The stream to write to.</param>
private void WriteColorTable<TPixel>(QuantizedFrame<TPixel> image, Stream stream)
private void WriteColorTable<TPixel>(IndexedImageFrame<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
// The maximum number of colors for the bit depth
int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3;
int pixelCount = image.Palette.Length;
int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * Unsafe.SizeOf<Rgb24>();

using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength))
{
PixelOperations<TPixel>.Instance.ToRgb24Bytes(
this.configuration,
image.Palette.Span,
colorTable.GetSpan(),
pixelCount);
stream.Write(colorTable.Array, 0, colorTableLength);
}
using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength, AllocationOptions.Clean);
PixelOperations<TPixel>.Instance.ToRgb24Bytes(
this.configuration,
image.Palette.Span,
colorTable.GetSpan(),
image.Palette.Length);

stream.Write(colorTable.Array, 0, colorTableLength);
}

/// <summary>
/// Writes the image pixel data to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="QuantizedFrame{TPixel}"/> containing indexed pixels.</param>
/// <param name="image">The <see cref="IndexedImageFrame{TPixel}"/> containing indexed pixels.</param>
/// <param name="stream">The stream to write to.</param>
private void WriteImageData<TPixel>(QuantizedFrame<TPixel> image, Stream stream)
private void WriteImageData<TPixel>(IndexedImageFrame<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
using (var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth))
{
encoder.Encode(image.GetPixelSpan(), stream);
}
using var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth);
encoder.Encode(image.GetPixelBufferSpan(), stream);
}
}
}
2 changes: 1 addition & 1 deletion src/ImageSharp/Formats/Gif/LzwEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ private void Compress(ReadOnlySpan<byte> indexedPixels, int initialBits, Stream

ent = this.NextPixel(indexedPixels);

// TODO: PERF: It looks likt hshift could be calculated once statically.
// TODO: PERF: It looks like hshift could be calculated once statically.
hshift = 0;
for (fcode = this.hsize; fcode < 65536; fcode *= 2)
{
Expand Down
Loading