Skip to content

Commit e4ba5d3

Browse files
Decode LZW row by row
1 parent 9dda64a commit e4ba5d3

File tree

9 files changed

+266
-289
lines changed

9 files changed

+266
-289
lines changed

src/ImageSharp/Formats/Gif/GifDecoderCore.cs

Lines changed: 95 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -427,68 +427,49 @@ private void ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
427427
{
428428
this.ReadImageDescriptor(stream);
429429

430-
Buffer2D<byte>? indices = null;
431-
try
432-
{
433-
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
434-
bool hasLocalColorTable = this.imageDescriptor.LocalColorTableFlag;
435-
436-
if (hasLocalColorTable)
437-
{
438-
// Read and store the local color table. We allocate the maximum possible size and slice to match.
439-
int length = this.currentLocalColorTableSize = this.imageDescriptor.LocalColorTableSize * 3;
440-
this.currentLocalColorTable ??= this.configuration.MemoryAllocator.Allocate<byte>(768, AllocationOptions.Clean);
441-
stream.Read(this.currentLocalColorTable.GetSpan()[..length]);
442-
}
443-
444-
indices = this.configuration.MemoryAllocator.Allocate2D<byte>(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean);
445-
this.ReadFrameIndices(stream, indices);
430+
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
431+
bool hasLocalColorTable = this.imageDescriptor.LocalColorTableFlag;
446432

447-
Span<byte> rawColorTable = default;
448-
if (hasLocalColorTable)
449-
{
450-
rawColorTable = this.currentLocalColorTable!.GetSpan()[..this.currentLocalColorTableSize];
451-
}
452-
else if (this.globalColorTable != null)
453-
{
454-
rawColorTable = this.globalColorTable.GetSpan();
455-
}
456-
457-
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>(rawColorTable);
458-
this.ReadFrameColors(ref image, ref previousFrame, indices, colorTable, this.imageDescriptor);
433+
if (hasLocalColorTable)
434+
{
435+
// Read and store the local color table. We allocate the maximum possible size and slice to match.
436+
int length = this.currentLocalColorTableSize = this.imageDescriptor.LocalColorTableSize * 3;
437+
this.currentLocalColorTable ??= this.configuration.MemoryAllocator.Allocate<byte>(768, AllocationOptions.Clean);
438+
stream.Read(this.currentLocalColorTable.GetSpan()[..length]);
439+
}
459440

460-
// Skip any remaining blocks
461-
SkipBlock(stream);
441+
Span<byte> rawColorTable = default;
442+
if (hasLocalColorTable)
443+
{
444+
rawColorTable = this.currentLocalColorTable!.GetSpan()[..this.currentLocalColorTableSize];
462445
}
463-
finally
446+
else if (this.globalColorTable != null)
464447
{
465-
indices?.Dispose();
448+
rawColorTable = this.globalColorTable.GetSpan();
466449
}
467-
}
468450

469-
/// <summary>
470-
/// Reads the frame indices marking the color to use for each pixel.
471-
/// </summary>
472-
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
473-
/// <param name="indices">The 2D pixel buffer to write to.</param>
474-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
475-
private void ReadFrameIndices(BufferedReadStream stream, Buffer2D<byte> indices)
476-
{
477-
int minCodeSize = stream.ReadByte();
478-
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream);
479-
lzwDecoder.DecodePixels(minCodeSize, indices);
451+
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>(rawColorTable);
452+
this.ReadFrameColors(stream, ref image, ref previousFrame, colorTable, this.imageDescriptor);
453+
454+
// Skip any remaining blocks
455+
SkipBlock(stream);
480456
}
481457

482458
/// <summary>
483459
/// Reads the frames colors, mapping indices to colors.
484460
/// </summary>
485461
/// <typeparam name="TPixel">The pixel format.</typeparam>
462+
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
486463
/// <param name="image">The image to decode the information to.</param>
487464
/// <param name="previousFrame">The previous frame.</param>
488-
/// <param name="indices">The indexed pixels.</param>
489465
/// <param name="colorTable">The color table containing the available colors.</param>
490466
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
491-
private void ReadFrameColors<TPixel>(ref Image<TPixel>? image, ref ImageFrame<TPixel>? previousFrame, Buffer2D<byte> indices, ReadOnlySpan<Rgb24> colorTable, in GifImageDescriptor descriptor)
467+
private void ReadFrameColors<TPixel>(
468+
BufferedReadStream stream,
469+
ref Image<TPixel>? image,
470+
ref ImageFrame<TPixel>? previousFrame,
471+
ReadOnlySpan<Rgb24> colorTable,
472+
in GifImageDescriptor descriptor)
492473
where TPixel : unmanaged, IPixel<TPixel>
493474
{
494475
int imageWidth = this.logicalScreenDescriptor.Width;
@@ -549,73 +530,83 @@ private void ReadFrameColors<TPixel>(ref Image<TPixel>? image, ref ImageFrame<TP
549530
byte transIndex = this.graphicsControlExtension.TransparencyIndex;
550531
int colorTableMaxIdx = colorTable.Length - 1;
551532

552-
for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++)
533+
// For a properly encoded gif the descriptor dimensions will never exceed the logical screen dimensions.
534+
// However we have images that exceed this that can be decoded by other libraries. #1530
535+
using IMemoryOwner<byte> indicesRowOwner = this.memoryAllocator.Allocate<byte>(descriptor.Width);
536+
Span<byte> indicesRow = indicesRowOwner.Memory.Span;
537+
ref byte indicesRowRef = ref MemoryMarshal.GetReference(indicesRow);
538+
539+
int minCodeSize = stream.ReadByte();
540+
if (LzwDecoder.IsValidMinCodeSize(minCodeSize))
553541
{
554-
ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.DangerousGetRowSpan(y - descriptorTop));
542+
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream, minCodeSize);
555543

556-
// Check if this image is interlaced.
557-
int writeY; // the target y offset to write to
558-
if (descriptor.InterlaceFlag)
544+
for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++)
559545
{
560-
// If so then we read lines at predetermined offsets.
561-
// When an entire image height worth of offset lines has been read we consider this a pass.
562-
// With each pass the number of offset lines changes and the starting line changes.
563-
if (interlaceY >= descriptor.Height)
546+
// Check if this image is interlaced.
547+
int writeY; // the target y offset to write to
548+
if (descriptor.InterlaceFlag)
564549
{
565-
interlacePass++;
566-
switch (interlacePass)
550+
// If so then we read lines at predetermined offsets.
551+
// When an entire image height worth of offset lines has been read we consider this a pass.
552+
// With each pass the number of offset lines changes and the starting line changes.
553+
if (interlaceY >= descriptor.Height)
567554
{
568-
case 1:
569-
interlaceY = 4;
570-
break;
571-
case 2:
572-
interlaceY = 2;
573-
interlaceIncrement = 4;
574-
break;
575-
case 3:
576-
interlaceY = 1;
577-
interlaceIncrement = 2;
578-
break;
555+
interlacePass++;
556+
switch (interlacePass)
557+
{
558+
case 1:
559+
interlaceY = 4;
560+
break;
561+
case 2:
562+
interlaceY = 2;
563+
interlaceIncrement = 4;
564+
break;
565+
case 3:
566+
interlaceY = 1;
567+
interlaceIncrement = 2;
568+
break;
569+
}
579570
}
580-
}
581571

582-
writeY = interlaceY + descriptor.Top;
583-
interlaceY += interlaceIncrement;
584-
}
585-
else
586-
{
587-
writeY = y;
588-
}
572+
writeY = Math.Min(interlaceY + descriptor.Top, image.Height);
573+
interlaceY += interlaceIncrement;
574+
}
575+
else
576+
{
577+
writeY = y;
578+
}
589579

590-
ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.PixelBuffer.DangerousGetRowSpan(writeY));
580+
lzwDecoder.DecodePixelRow(indicesRow);
581+
ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.PixelBuffer.DangerousGetRowSpan(writeY));
591582

592-
if (!transFlag)
593-
{
594-
// #403 The left + width value can be larger than the image width
595-
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
583+
if (!transFlag)
596584
{
597-
int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, (uint)(x - descriptorLeft)), 0, colorTableMaxIdx);
598-
ref TPixel pixel = ref Unsafe.Add(ref rowRef, (uint)x);
599-
Rgb24 rgb = colorTable[index];
600-
pixel.FromRgb24(rgb);
585+
// #403 The left + width value can be larger than the image width
586+
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
587+
{
588+
int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, (uint)(x - descriptorLeft)), 0, colorTableMaxIdx);
589+
ref TPixel pixel = ref Unsafe.Add(ref rowRef, (uint)x);
590+
Rgb24 rgb = colorTable[index];
591+
pixel.FromRgb24(rgb);
592+
}
601593
}
602-
}
603-
else
604-
{
605-
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
594+
else
606595
{
607-
int rawIndex = Unsafe.Add(ref indicesRowRef, (uint)(x - descriptorLeft));
608-
609-
// Treat any out of bounds values as transparent.
610-
if (rawIndex > colorTableMaxIdx || rawIndex == transIndex)
596+
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
611597
{
612-
continue;
613-
}
598+
int index = Unsafe.Add(ref indicesRowRef, (uint)(x - descriptorLeft));
599+
600+
// Treat any out of bounds values as transparent.
601+
if (index > colorTableMaxIdx || index == transIndex)
602+
{
603+
continue;
604+
}
614605

615-
int index = Numerics.Clamp(rawIndex, 0, colorTableMaxIdx);
616-
ref TPixel pixel = ref Unsafe.Add(ref rowRef, (uint)x);
617-
Rgb24 rgb = colorTable[index];
618-
pixel.FromRgb24(rgb);
606+
ref TPixel pixel = ref Unsafe.Add(ref rowRef, (uint)x);
607+
Rgb24 rgb = colorTable[index];
608+
pixel.FromRgb24(rgb);
609+
}
619610
}
620611
}
621612
}
@@ -656,8 +647,11 @@ private void ReadFrameMetadata(BufferedReadStream stream, List<ImageFrameMetadat
656647
// Skip the frame indices. Pixels length + mincode size.
657648
// The gif format does not tell us the length of the compressed data beforehand.
658649
int minCodeSize = stream.ReadByte();
659-
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream);
660-
lzwDecoder.SkipIndices(minCodeSize, this.imageDescriptor.Width * this.imageDescriptor.Height);
650+
if (LzwDecoder.IsValidMinCodeSize(minCodeSize))
651+
{
652+
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream, minCodeSize);
653+
lzwDecoder.SkipIndices(this.imageDescriptor.Width * this.imageDescriptor.Height);
654+
}
661655

662656
ImageFrameMetadata currentFrame = new();
663657
frameMetadata.Add(currentFrame);

0 commit comments

Comments
 (0)