Skip to content

Commit e771eb4

Browse files
Merge branch 'master' into patch-1
2 parents 0e83ee3 + fab07ab commit e771eb4

34 files changed

+706
-627
lines changed

src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ public void Convert(ReadOnlySpan<Lms> source, Span<LinearRgb> destination)
248248
}
249249

250250
/// <summary>
251-
/// Performs the bulk conversion from <see cref="Lms"/> into <see cref="LinearRgb"/>.
251+
/// Performs the bulk conversion from <see cref="Rgb"/> into <see cref="LinearRgb"/>.
252252
/// </summary>
253253
/// <param name="source">The span to the source colors</param>
254254
/// <param name="destination">The span to the destination colors</param>
@@ -435,4 +435,4 @@ public LinearRgb ToLinearRgb(in YCbCr color)
435435
return this.ToLinearRgb(rgb);
436436
}
437437
}
438-
}
438+
}

src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,8 @@ private void Write8Bit<TPixel>(Stream stream, ImageFrame<TPixel> image)
336336
private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
337337
where TPixel : unmanaged, IPixel<TPixel>
338338
{
339-
using IFrameQuantizer<TPixel> quantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration);
340-
using QuantizedFrame<TPixel> quantized = quantizer.QuantizeFrame(image, image.Bounds());
339+
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration);
340+
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image, image.Bounds());
341341

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

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

366366
for (int i = 0; i < this.padding; i++)

src/ImageSharp/Formats/Gif/GifEncoder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.IO;
55
using SixLabors.ImageSharp.Advanced;
66
using SixLabors.ImageSharp.PixelFormats;
7+
using SixLabors.ImageSharp.Processing;
78
using SixLabors.ImageSharp.Processing.Processors.Quantization;
89

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

2223
/// <summary>
2324
/// Gets or sets the color table mode: Global or local.

src/ImageSharp/Formats/Gif/GifEncoderCore.cs

Lines changed: 57 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,14 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
7979
bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global;
8080

8181
// Quantize the image returning a palette.
82-
QuantizedFrame<TPixel> quantized;
82+
IndexedImageFrame<TPixel> quantized;
8383
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration))
8484
{
8585
quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
8686
}
8787

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

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

121121
// Clean up.
122-
quantized?.Dispose();
122+
quantized.Dispose();
123123

124124
// TODO: Write extension etc
125125
stream.WriteByte(GifConstants.EndIntroducer);
126126
}
127127

128-
private void EncodeGlobal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> quantized, int transparencyIndex, Stream stream)
128+
private void EncodeGlobal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, int transparencyIndex, Stream stream)
129129
where TPixel : unmanaged, IPixel<TPixel>
130130
{
131+
// The palette quantizer can reuse the same pixel map across multiple frames
132+
// since the palette is unchanging. This allows a reduction of memory usage across
133+
// multi frame gifs using a global palette.
134+
EuclideanPixelMap<TPixel> pixelMap = default;
135+
bool pixelMapSet = false;
131136
for (int i = 0; i < image.Frames.Count; i++)
132137
{
133138
ImageFrame<TPixel> frame = image.Frames[i];
@@ -142,22 +147,27 @@ private void EncodeGlobal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> qu
142147
}
143148
else
144149
{
145-
using (var paletteFrameQuantizer = new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Options, quantized.Palette))
146-
using (QuantizedFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()))
150+
if (!pixelMapSet)
147151
{
148-
this.WriteImageData(paletteQuantized, stream);
152+
pixelMapSet = true;
153+
pixelMap = new EuclideanPixelMap<TPixel>(this.configuration, quantized.Palette);
149154
}
155+
156+
using var paletteFrameQuantizer = new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap);
157+
using IndexedImageFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds());
158+
this.WriteImageData(paletteQuantized, stream);
150159
}
151160
}
152161
}
153162

154-
private void EncodeLocal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> quantized, Stream stream)
163+
private void EncodeLocal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, Stream stream)
155164
where TPixel : unmanaged, IPixel<TPixel>
156165
{
157166
ImageFrame<TPixel> previousFrame = null;
158167
GifFrameMetadata previousMeta = null;
159-
foreach (ImageFrame<TPixel> frame in image.Frames)
168+
for (int i = 0; i < image.Frames.Count; i++)
160169
{
170+
ImageFrame<TPixel> frame = image.Frames[i];
161171
ImageFrameMetadata metadata = frame.Metadata;
162172
GifFrameMetadata frameMetadata = metadata.GetGifMetadata();
163173
if (quantized is null)
@@ -173,27 +183,23 @@ private void EncodeLocal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> qua
173183
MaxColors = frameMetadata.ColorTableLength
174184
};
175185

176-
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, options))
177-
{
178-
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
179-
}
186+
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, options);
187+
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
180188
}
181189
else
182190
{
183-
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration))
184-
{
185-
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
186-
}
191+
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration);
192+
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
187193
}
188194
}
189195

190-
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
196+
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length);
191197
this.WriteGraphicalControlExtension(frameMetadata, this.GetTransparentIndex(quantized), stream);
192198
this.WriteImageDescriptor(frame, true, stream);
193199
this.WriteColorTable(quantized, stream);
194200
this.WriteImageData(quantized, stream);
195201

196-
quantized?.Dispose();
202+
quantized.Dispose();
197203
quantized = null; // So next frame can regenerate it
198204
previousFrame = frame;
199205
previousMeta = frameMetadata;
@@ -208,25 +214,23 @@ private void EncodeLocal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> qua
208214
/// <returns>
209215
/// The <see cref="int"/>.
210216
/// </returns>
211-
private int GetTransparentIndex<TPixel>(QuantizedFrame<TPixel> quantized)
217+
private int GetTransparentIndex<TPixel>(IndexedImageFrame<TPixel> quantized)
212218
where TPixel : unmanaged, IPixel<TPixel>
213219
{
214-
// Transparent pixels are much more likely to be found at the end of a palette
220+
// Transparent pixels are much more likely to be found at the end of a palette.
215221
int index = -1;
216-
int length = quantized.Palette.Length;
222+
ReadOnlySpan<TPixel> paletteSpan = quantized.Palette.Span;
217223

218-
using (IMemoryOwner<Rgba32> rgbaBuffer = this.memoryAllocator.Allocate<Rgba32>(length))
219-
{
220-
Span<Rgba32> rgbaSpan = rgbaBuffer.GetSpan();
221-
ref Rgba32 paletteRef = ref MemoryMarshal.GetReference(rgbaSpan);
222-
PixelOperations<TPixel>.Instance.ToRgba32(this.configuration, quantized.Palette.Span, rgbaSpan);
224+
using IMemoryOwner<Rgba32> rgbaOwner = quantized.Configuration.MemoryAllocator.Allocate<Rgba32>(paletteSpan.Length);
225+
Span<Rgba32> rgbaSpan = rgbaOwner.GetSpan();
226+
PixelOperations<TPixel>.Instance.ToRgba32(quantized.Configuration, paletteSpan, rgbaSpan);
227+
ref Rgba32 rgbaSpanRef = ref MemoryMarshal.GetReference(rgbaSpan);
223228

224-
for (int i = quantized.Palette.Length - 1; i >= 0; i--)
229+
for (int i = rgbaSpan.Length - 1; i >= 0; i--)
230+
{
231+
if (Unsafe.Add(ref rgbaSpanRef, i).Equals(default))
225232
{
226-
if (Unsafe.Add(ref paletteRef, i).Equals(default))
227-
{
228-
index = i;
229-
}
233+
index = i;
230234
}
231235
}
232236

@@ -326,16 +330,19 @@ private void WriteComments(GifMetadata metadata, Stream stream)
326330
return;
327331
}
328332

329-
foreach (string comment in metadata.Comments)
333+
for (var i = 0; i < metadata.Comments.Count; i++)
330334
{
335+
string comment = metadata.Comments[i];
331336
this.buffer[0] = GifConstants.ExtensionIntroducer;
332337
this.buffer[1] = GifConstants.CommentLabel;
333338
stream.Write(this.buffer, 0, 2);
334339

335340
// Comment will be stored in chunks of 255 bytes, if it exceeds this size.
336341
ReadOnlySpan<char> commentSpan = comment.AsSpan();
337342
int idx = 0;
338-
for (; idx <= comment.Length - GifConstants.MaxCommentSubBlockLength; idx += GifConstants.MaxCommentSubBlockLength)
343+
for (;
344+
idx <= comment.Length - GifConstants.MaxCommentSubBlockLength;
345+
idx += GifConstants.MaxCommentSubBlockLength)
339346
{
340347
WriteCommentSubBlock(stream, commentSpan, idx, GifConstants.MaxCommentSubBlockLength);
341348
}
@@ -391,7 +398,8 @@ private void WriteGraphicalControlExtension(GifFrameMetadata metadata, int trans
391398
/// </summary>
392399
/// <param name="extension">The extension to write to the stream.</param>
393400
/// <param name="stream">The stream to write to.</param>
394-
public void WriteExtension(IGifExtension extension, Stream stream)
401+
private void WriteExtension<TGifExtension>(TGifExtension extension, Stream stream)
402+
where TGifExtension : struct, IGifExtension
395403
{
396404
this.buffer[0] = GifConstants.ExtensionIntroducer;
397405
this.buffer[1] = extension.Label;
@@ -437,37 +445,33 @@ private void WriteImageDescriptor<TPixel>(ImageFrame<TPixel> image, bool hasColo
437445
/// <typeparam name="TPixel">The pixel format.</typeparam>
438446
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode.</param>
439447
/// <param name="stream">The stream to write to.</param>
440-
private void WriteColorTable<TPixel>(QuantizedFrame<TPixel> image, Stream stream)
448+
private void WriteColorTable<TPixel>(IndexedImageFrame<TPixel> image, Stream stream)
441449
where TPixel : unmanaged, IPixel<TPixel>
442450
{
443451
// The maximum number of colors for the bit depth
444-
int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3;
445-
int pixelCount = image.Palette.Length;
452+
int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * Unsafe.SizeOf<Rgb24>();
446453

447-
using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength))
448-
{
449-
PixelOperations<TPixel>.Instance.ToRgb24Bytes(
450-
this.configuration,
451-
image.Palette.Span,
452-
colorTable.GetSpan(),
453-
pixelCount);
454-
stream.Write(colorTable.Array, 0, colorTableLength);
455-
}
454+
using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength, AllocationOptions.Clean);
455+
PixelOperations<TPixel>.Instance.ToRgb24Bytes(
456+
this.configuration,
457+
image.Palette.Span,
458+
colorTable.GetSpan(),
459+
image.Palette.Length);
460+
461+
stream.Write(colorTable.Array, 0, colorTableLength);
456462
}
457463

458464
/// <summary>
459465
/// Writes the image pixel data to the stream.
460466
/// </summary>
461467
/// <typeparam name="TPixel">The pixel format.</typeparam>
462-
/// <param name="image">The <see cref="QuantizedFrame{TPixel}"/> containing indexed pixels.</param>
468+
/// <param name="image">The <see cref="IndexedImageFrame{TPixel}"/> containing indexed pixels.</param>
463469
/// <param name="stream">The stream to write to.</param>
464-
private void WriteImageData<TPixel>(QuantizedFrame<TPixel> image, Stream stream)
470+
private void WriteImageData<TPixel>(IndexedImageFrame<TPixel> image, Stream stream)
465471
where TPixel : unmanaged, IPixel<TPixel>
466472
{
467-
using (var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth))
468-
{
469-
encoder.Encode(image.GetPixelSpan(), stream);
470-
}
473+
using var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth);
474+
encoder.Encode(image.GetPixelBufferSpan(), stream);
471475
}
472476
}
473477
}

src/ImageSharp/Formats/Gif/LzwEncoder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ private void Compress(ReadOnlySpan<byte> indexedPixels, int initialBits, Stream
274274

275275
ent = this.NextPixel(indexedPixels);
276276

277-
// TODO: PERF: It looks likt hshift could be calculated once statically.
277+
// TODO: PERF: It looks like hshift could be calculated once statically.
278278
hshift = 0;
279279
for (fcode = this.hsize; fcode < 65536; fcode *= 2)
280280
{

0 commit comments

Comments
 (0)