Skip to content

Commit 4b910e7

Browse files
JimBobSquarePantsantonfirsov
authored andcommitted
Decode LZW row by row
Signed-off-by: antonfirsov <antonfir@gmail.com>
1 parent 7ce329a commit 4b910e7

File tree

9 files changed

+238
-158
lines changed

9 files changed

+238
-158
lines changed

src/ImageSharp/Formats/Gif/GifDecoderCore.cs

+58-55
Original file line numberDiff line numberDiff line change
@@ -377,66 +377,50 @@ private void ReadFrame<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> p
377377
{
378378
this.ReadImageDescriptor();
379379

380-
IMemoryOwner<byte> localColorTable = null;
381380
Buffer2D<byte> indices = null;
382-
try
383-
{
384-
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
385-
if (this.imageDescriptor.LocalColorTableFlag)
386-
{
387-
int length = this.imageDescriptor.LocalColorTableSize * 3;
388-
localColorTable = this.Configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
389-
this.stream.Read(localColorTable.GetSpan());
390-
}
391-
392-
indices = this.Configuration.MemoryAllocator.Allocate2D<byte>(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean);
393-
this.ReadFrameIndices(indices);
394-
395-
Span<byte> rawColorTable = default;
396-
if (localColorTable != null)
397-
{
398-
rawColorTable = localColorTable.GetSpan();
399-
}
400-
else if (this.globalColorTable != null)
401-
{
402-
rawColorTable = this.globalColorTable.GetSpan();
403-
}
404-
405-
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>(rawColorTable);
406-
this.ReadFrameColors(ref image, ref previousFrame, indices, colorTable, this.imageDescriptor);
407-
408-
// Skip any remaining blocks
409-
this.SkipBlock();
410-
}
411-
finally
412-
{
413-
localColorTable?.Dispose();
414-
indices?.Dispose();
415-
}
381+
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
382+
bool hasLocalColorTable = this.imageDescriptor.LocalColorTableFlag;
383+
if (hasLocalColorTable)
384+
{
385+
// Read and store the local color table. We allocate the maximum possible size and slice to match.
386+
int length = this.currentLocalColorTableSize = this.imageDescriptor.LocalColorTableSize * 3;
387+
this.currentLocalColorTable ??= this.configuration.MemoryAllocator.Allocate<byte>(768, AllocationOptions.Clean);
388+
stream.Read(this.currentLocalColorTable.GetSpan()[..length]);
416389
}
417390

418-
/// <summary>
419-
/// Reads the frame indices marking the color to use for each pixel.
420-
/// </summary>
421-
/// <param name="indices">The 2D pixel buffer to write to.</param>
422-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
423-
private void ReadFrameIndices(Buffer2D<byte> indices)
391+
Span<byte> rawColorTable = default;
392+
if (hasLocalColorTable)
393+
{
394+
rawColorTable = this.currentLocalColorTable!.GetSpan()[..this.currentLocalColorTableSize];
395+
}
396+
else if (this.globalColorTable != null)
424397
{
425-
int minCodeSize = this.stream.ReadByte();
426-
using var lzwDecoder = new LzwDecoder(this.Configuration.MemoryAllocator, this.stream);
427-
lzwDecoder.DecodePixels(minCodeSize, indices);
398+
rawColorTable = this.globalColorTable.GetSpan();
428399
}
429400

401+
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>(rawColorTable);
402+
this.ReadFrameColors(stream, ref image, ref previousFrame, colorTable, this.imageDescriptor);
403+
404+
// Skip any remaining blocks
405+
SkipBlock(stream);
406+
}
407+
localColorTable?.Dispose();
408+
430409
/// <summary>
431410
/// Reads the frames colors, mapping indices to colors.
432411
/// </summary>
433412
/// <typeparam name="TPixel">The pixel format.</typeparam>
413+
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
434414
/// <param name="image">The image to decode the information to.</param>
435415
/// <param name="previousFrame">The previous frame.</param>
436-
/// <param name="indices">The indexed pixels.</param>
437416
/// <param name="colorTable">The color table containing the available colors.</param>
438417
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
439-
private void ReadFrameColors<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame, Buffer2D<byte> indices, ReadOnlySpan<Rgb24> colorTable, in GifImageDescriptor descriptor)
418+
private void ReadFrameColors<TPixel>(
419+
BufferedReadStream stream,
420+
ref Image<TPixel>? image,
421+
ref ImageFrame<TPixel>? previousFrame,
422+
ReadOnlySpan<Rgb24> colorTable,
423+
in GifImageDescriptor descriptor)
440424
where TPixel : unmanaged, IPixel<TPixel>
441425
{
442426
int imageWidth = this.logicalScreenDescriptor.Width;
@@ -494,10 +478,19 @@ private void ReadFrameColors<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPi
494478
byte transIndex = this.graphicsControlExtension.TransparencyIndex;
495479
int colorTableMaxIdx = colorTable.Length - 1;
496480

497-
for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++)
481+
// For a properly encoded gif the descriptor dimensions will never exceed the logical screen dimensions.
482+
// However we have images that exceed this that can be decoded by other libraries. #1530
483+
using IMemoryOwner<byte> indicesRowOwner = this.memoryAllocator.Allocate<byte>(descriptor.Width);
484+
Span<byte> indicesRow = indicesRowOwner.Memory.Span;
485+
ref byte indicesRowRef = ref MemoryMarshal.GetReference(indicesRow);
486+
487+
int minCodeSize = stream.ReadByte();
488+
if (LzwDecoder.IsValidMinCodeSize(minCodeSize))
498489
{
499-
ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.DangerousGetRowSpan(y - descriptorTop));
490+
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream, minCodeSize);
500491

492+
for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++)
493+
{
501494
// Check if this image is interlaced.
502495
int writeY; // the target y offset to write to
503496
if (descriptor.InterlaceFlag)
@@ -524,23 +517,24 @@ private void ReadFrameColors<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPi
524517
}
525518
}
526519

527-
writeY = interlaceY + descriptor.Top;
520+
writeY = Math.Min(interlaceY + descriptor.Top, image.Height);
528521
interlaceY += interlaceIncrement;
529522
}
530523
else
531524
{
532525
writeY = y;
533526
}
534527

528+
lzwDecoder.DecodePixelRow(indicesRow);
535529
ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.PixelBuffer.DangerousGetRowSpan(writeY));
536530

537531
if (!transFlag)
538532
{
539533
// #403 The left + width value can be larger than the image width
540534
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
541535
{
542-
int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, x - descriptorLeft), 0, colorTableMaxIdx);
543-
ref TPixel pixel = ref Unsafe.Add(ref rowRef, x);
536+
int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, (uint)(x - descriptorLeft)), 0, colorTableMaxIdx);
537+
ref TPixel pixel = ref Unsafe.Add(ref rowRef, (uint)x);
544538
Rgb24 rgb = colorTable[index];
545539
pixel.FromRgb24(rgb);
546540
}
@@ -549,16 +543,20 @@ private void ReadFrameColors<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPi
549543
{
550544
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
551545
{
552-
int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, x - descriptorLeft), 0, colorTableMaxIdx);
546+
int index = Unsafe.Add(ref indicesRowRef, (uint)(x - descriptorLeft));
553547
if (transIndex != index)
548+
// Treat any out of bounds values as transparent.
549+
if (index > colorTableMaxIdx || index == transIndex)
554550
{
555-
ref TPixel pixel = ref Unsafe.Add(ref rowRef, x);
556-
Rgb24 rgb = colorTable[index];
557-
pixel.FromRgb24(rgb);
551+
continue;
558552
}
553+
ref TPixel pixel = ref Unsafe.Add(ref rowRef, (uint)x);
554+
Rgb24 rgb = colorTable[index];
555+
pixel.FromRgb24(rgb);
559556
}
560557
}
561558
}
559+
}
562560

563561
if (prevFrame != null)
564562
{
@@ -575,6 +573,11 @@ private void ReadFrameColors<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPi
575573
}
576574

577575
/// <summary>
576+
if (LzwDecoder.IsValidMinCodeSize(minCodeSize))
577+
{
578+
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream, minCodeSize);
579+
lzwDecoder.SkipIndices(this.imageDescriptor.Width * this.imageDescriptor.Height);
580+
}
578581
/// Restores the current frame area to the background.
579582
/// </summary>
580583
/// <typeparam name="TPixel">The pixel format.</typeparam>

0 commit comments

Comments
 (0)