Skip to content

Commit 9840a23

Browse files
Merge branch 'release/3.1.x' into js/mono-aot-decoder-workaround
2 parents dc6871c + 1d4a287 commit 9840a23

File tree

81 files changed

+732
-494
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+732
-494
lines changed

src/ImageSharp/Formats/Gif/GifDecoderCore.cs

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

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

442-
Span<byte> rawColorTable = default;
443-
if (hasLocalColorTable)
444-
{
445-
rawColorTable = this.currentLocalColorTable!.GetSpan()[..this.currentLocalColorTableSize];
446-
}
447-
else if (this.globalColorTable != null)
448-
{
449-
rawColorTable = this.globalColorTable.GetSpan();
450-
}
451-
452-
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>(rawColorTable);
453-
this.ReadFrameColors(ref image, ref previousFrame, indices, colorTable, this.imageDescriptor);
428+
if (hasLocalColorTable)
429+
{
430+
// Read and store the local color table. We allocate the maximum possible size and slice to match.
431+
int length = this.currentLocalColorTableSize = this.imageDescriptor.LocalColorTableSize * 3;
432+
this.currentLocalColorTable ??= this.configuration.MemoryAllocator.Allocate<byte>(768, AllocationOptions.Clean);
433+
stream.Read(this.currentLocalColorTable.GetSpan()[..length]);
434+
}
454435

455-
// Skip any remaining blocks
456-
SkipBlock(stream);
436+
Span<byte> rawColorTable = default;
437+
if (hasLocalColorTable)
438+
{
439+
rawColorTable = this.currentLocalColorTable!.GetSpan()[..this.currentLocalColorTableSize];
457440
}
458-
finally
441+
else if (this.globalColorTable != null)
459442
{
460-
indices?.Dispose();
443+
rawColorTable = this.globalColorTable.GetSpan();
461444
}
462-
}
463445

464-
/// <summary>
465-
/// Reads the frame indices marking the color to use for each pixel.
466-
/// </summary>
467-
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
468-
/// <param name="indices">The 2D pixel buffer to write to.</param>
469-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
470-
private void ReadFrameIndices(BufferedReadStream stream, Buffer2D<byte> indices)
471-
{
472-
int minCodeSize = stream.ReadByte();
473-
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream);
474-
lzwDecoder.DecodePixels(minCodeSize, indices);
446+
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>(rawColorTable);
447+
this.ReadFrameColors(stream, ref image, ref previousFrame, colorTable, this.imageDescriptor);
448+
449+
// Skip any remaining blocks
450+
SkipBlock(stream);
475451
}
476452

477453
/// <summary>
478454
/// Reads the frames colors, mapping indices to colors.
479455
/// </summary>
480456
/// <typeparam name="TPixel">The pixel format.</typeparam>
457+
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
481458
/// <param name="image">The image to decode the information to.</param>
482459
/// <param name="previousFrame">The previous frame.</param>
483-
/// <param name="indices">The indexed pixels.</param>
484460
/// <param name="colorTable">The color table containing the available colors.</param>
485461
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
486-
private void ReadFrameColors<TPixel>(ref Image<TPixel>? image, ref ImageFrame<TPixel>? previousFrame, Buffer2D<byte> indices, ReadOnlySpan<Rgb24> colorTable, in GifImageDescriptor descriptor)
462+
private void ReadFrameColors<TPixel>(
463+
BufferedReadStream stream,
464+
ref Image<TPixel>? image,
465+
ref ImageFrame<TPixel>? previousFrame,
466+
ReadOnlySpan<Rgb24> colorTable,
467+
in GifImageDescriptor descriptor)
487468
where TPixel : unmanaged, IPixel<TPixel>
488469
{
489470
int imageWidth = this.logicalScreenDescriptor.Width;
@@ -544,73 +525,83 @@ private void ReadFrameColors<TPixel>(ref Image<TPixel>? image, ref ImageFrame<TP
544525
byte transIndex = this.graphicsControlExtension.TransparencyIndex;
545526
int colorTableMaxIdx = colorTable.Length - 1;
546527

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

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

577-
writeY = interlaceY + descriptor.Top;
578-
interlaceY += interlaceIncrement;
579-
}
580-
else
581-
{
582-
writeY = y;
583-
}
567+
writeY = Math.Min(interlaceY + descriptor.Top, image.Height);
568+
interlaceY += interlaceIncrement;
569+
}
570+
else
571+
{
572+
writeY = y;
573+
}
584574

585-
ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.PixelBuffer.DangerousGetRowSpan(writeY));
575+
lzwDecoder.DecodePixelRow(indicesRow);
576+
ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.PixelBuffer.DangerousGetRowSpan(writeY));
586577

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

610-
int index = Numerics.Clamp(rawIndex, 0, colorTableMaxIdx);
611-
ref TPixel pixel = ref Unsafe.Add(ref rowRef, (uint)x);
612-
Rgb24 rgb = colorTable[index];
613-
pixel.FromRgb24(rgb);
601+
ref TPixel pixel = ref Unsafe.Add(ref rowRef, (uint)x);
602+
Rgb24 rgb = colorTable[index];
603+
pixel.FromRgb24(rgb);
604+
}
614605
}
615606
}
616607
}
@@ -651,8 +642,11 @@ private void ReadFrameMetadata(BufferedReadStream stream, List<ImageFrameMetadat
651642
// Skip the frame indices. Pixels length + mincode size.
652643
// The gif format does not tell us the length of the compressed data beforehand.
653644
int minCodeSize = stream.ReadByte();
654-
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream);
655-
lzwDecoder.SkipIndices(minCodeSize, this.imageDescriptor.Width * this.imageDescriptor.Height);
645+
if (LzwDecoder.IsValidMinCodeSize(minCodeSize))
646+
{
647+
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream, minCodeSize);
648+
lzwDecoder.SkipIndices(this.imageDescriptor.Width * this.imageDescriptor.Height);
649+
}
656650

657651
ImageFrameMetadata currentFrame = new();
658652
frameMetadata.Add(currentFrame);

0 commit comments

Comments
 (0)