Skip to content

Commit e08651b

Browse files
Merge pull request #2851 from SixLabors/af/safe-GifDecoder
Remove Unsafe usage from `GifDecoderCore` and optimize loops
2 parents 4be2645 + b38146d commit e08651b

File tree

2 files changed

+20
-21
lines changed

2 files changed

+20
-21
lines changed

src/ImageSharp/Formats/Gif/GifDecoderCore.cs

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -423,27 +423,22 @@ private void ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
423423

424424
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
425425
bool hasLocalColorTable = this.imageDescriptor.LocalColorTableFlag;
426-
426+
Span<byte> rawColorTable = default;
427427
if (hasLocalColorTable)
428428
{
429429
// Read and store the local color table. We allocate the maximum possible size and slice to match.
430430
int length = this.currentLocalColorTableSize = this.imageDescriptor.LocalColorTableSize * 3;
431431
this.currentLocalColorTable ??= this.configuration.MemoryAllocator.Allocate<byte>(768, AllocationOptions.Clean);
432432
stream.Read(this.currentLocalColorTable.GetSpan()[..length]);
433-
}
434-
435-
Span<byte> rawColorTable = default;
436-
if (hasLocalColorTable)
437-
{
438-
rawColorTable = this.currentLocalColorTable!.GetSpan()[..this.currentLocalColorTableSize];
433+
rawColorTable = this.currentLocalColorTable!.GetSpan()[..length];
439434
}
440435
else if (this.globalColorTable != null)
441436
{
442437
rawColorTable = this.globalColorTable.GetSpan();
443438
}
444439

445440
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>(rawColorTable);
446-
this.ReadFrameColors(stream, ref image, ref previousFrame, colorTable, this.imageDescriptor);
441+
this.ReadFrameColors(stream, ref image, ref previousFrame, colorTable);
447442

448443
// Skip any remaining blocks
449444
SkipBlock(stream);
@@ -457,15 +452,14 @@ private void ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
457452
/// <param name="image">The image to decode the information to.</param>
458453
/// <param name="previousFrame">The previous frame.</param>
459454
/// <param name="colorTable">The color table containing the available colors.</param>
460-
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
461455
private void ReadFrameColors<TPixel>(
462456
BufferedReadStream stream,
463457
ref Image<TPixel>? image,
464458
ref ImageFrame<TPixel>? previousFrame,
465-
ReadOnlySpan<Rgb24> colorTable,
466-
in GifImageDescriptor descriptor)
459+
ReadOnlySpan<Rgb24> colorTable)
467460
where TPixel : unmanaged, IPixel<TPixel>
468461
{
462+
GifImageDescriptor descriptor = this.imageDescriptor;
469463
int imageWidth = this.logicalScreenDescriptor.Width;
470464
int imageHeight = this.logicalScreenDescriptor.Height;
471465
bool transFlag = this.graphicsControlExtension.TransparencyFlag;
@@ -528,7 +522,6 @@ private void ReadFrameColors<TPixel>(
528522
// However we have images that exceed this that can be decoded by other libraries. #1530
529523
using IMemoryOwner<byte> indicesRowOwner = this.memoryAllocator.Allocate<byte>(descriptor.Width);
530524
Span<byte> indicesRow = indicesRowOwner.Memory.Span;
531-
ref byte indicesRowRef = ref MemoryMarshal.GetReference(indicesRow);
532525

533526
int minCodeSize = stream.ReadByte();
534527
if (LzwDecoder.IsValidMinCodeSize(minCodeSize))
@@ -572,30 +565,36 @@ private void ReadFrameColors<TPixel>(
572565
}
573566

574567
lzwDecoder.DecodePixelRow(indicesRow);
575-
ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.PixelBuffer.DangerousGetRowSpan(writeY));
568+
569+
// #403 The left + width value can be larger than the image width
570+
int maxX = Math.Min(descriptorRight, imageWidth);
571+
Span<TPixel> row = imageFrame.PixelBuffer.DangerousGetRowSpan(writeY);
572+
573+
// Take the descriptorLeft..maxX slice of the row, so the loop can be simplified.
574+
row = row[descriptorLeft..maxX];
576575

577576
if (!transFlag)
578577
{
579-
// #403 The left + width value can be larger than the image width
580-
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
578+
for (int x = 0; x < row.Length; x++)
581579
{
582-
int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, (uint)(x - descriptorLeft)), 0, colorTableMaxIdx);
583-
Unsafe.Add(ref rowRef, (uint)x) = TPixel.FromRgb24(colorTable[index]);
580+
int index = indicesRow[x];
581+
index = Numerics.Clamp(index, 0, colorTableMaxIdx);
582+
row[x] = TPixel.FromRgb24(colorTable[index]);
584583
}
585584
}
586585
else
587586
{
588-
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
587+
for (int x = 0; x < row.Length; x++)
589588
{
590-
int index = Unsafe.Add(ref indicesRowRef, (uint)(x - descriptorLeft));
589+
int index = indicesRow[x];
591590

592591
// Treat any out of bounds values as transparent.
593592
if (index > colorTableMaxIdx || index == transIndex)
594593
{
595594
continue;
596595
}
597596

598-
Unsafe.Add(ref rowRef, (uint)x) = TPixel.FromRgb24(colorTable[index]);
597+
row[x] = TPixel.FromRgb24(colorTable[index]);
599598
}
600599
}
601600
}

tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeGif.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public void ReadImages()
2626
}
2727
}
2828

29-
[Params(TestImages.Gif.Rings)]
29+
[Params(TestImages.Gif.Cheers)]
3030
public string TestImage { get; set; }
3131

3232
[Benchmark(Baseline = true, Description = "System.Drawing Gif")]

0 commit comments

Comments
 (0)