Skip to content

Commit

Permalink
Merge pull request #2605 from SixLabors/js/fix-gif-edge-case
Browse files Browse the repository at this point in the history
Handle dedup of local palette of 256 length
  • Loading branch information
JimBobSquarePants committed Dec 8, 2023
2 parents 07fd936 + f3ff11e commit cb07e36
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 13 deletions.
40 changes: 32 additions & 8 deletions src/ImageSharp/Formats/Gif/GifEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -380,18 +380,42 @@ private static GifFrameMetadata GetGifFrameMetadata<TPixel>(ImageFrame<TPixel> f
// We can use the color data from the decoded metadata here.
// We avoid dithering by default to preserve the original colors.
ReadOnlyMemory<Color> palette = metadata.LocalColorTable.Value;

if (hasDuplicates && !metadata.HasTransparency)
{
// A difference was captured but the metadata does not have transparency.
// Duplicates were captured but the metadata does not have transparency.
metadata.HasTransparency = true;
transparencyIndex = palette.Length;
metadata.TransparencyIndex = ClampIndex(transparencyIndex);
}

PaletteQuantizer quantizer = new(palette, new() { Dither = null }, transparencyIndex);
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, quantizer.Options);
quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds);
if (palette.Length < 256)
{
// We can use the existing palette and set the transparent index as the length.
// decoders will ignore this value.
transparencyIndex = palette.Length;
metadata.TransparencyIndex = ClampIndex(transparencyIndex);

PaletteQuantizer quantizer = new(palette, new() { Dither = null }, transparencyIndex);
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, quantizer.Options);
quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds);
}
else
{
// We must quantize the frame to generate a local color table.
IQuantizer quantizer = this.hasQuantizer ? this.quantizer! : KnownQuantizers.Octree;
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, quantizer.Options);
quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds);

// The transparency index derived by the quantizer will differ from the index
// within the metadata. We need to update the metadata to reflect this.
int derivedTransparencyIndex = GetTransparentIndex(quantized, null);
metadata.TransparencyIndex = ClampIndex(derivedTransparencyIndex);
}
}
else
{
// Just use the local palette.
PaletteQuantizer quantizer = new(palette, new() { Dither = null }, transparencyIndex);
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, quantizer.Options);
quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds);
}
}
else
{
Expand Down
10 changes: 8 additions & 2 deletions src/ImageSharp/Formats/Gif/MetadataExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,20 @@ internal static AnimatedImageMetadata ToAnimatedImageMetadata(this GifMetadata s
}

internal static AnimatedImageFrameMetadata ToAnimatedImageFrameMetadata(this GifFrameMetadata source)
=> new()
{
// For most scenarios we would consider the blend method to be 'Over' however if a frame has a disposal method of 'RestoreToBackground' or
// has a local palette with 256 colors and is not transparent we should use 'Source'.
bool blendSource = source.DisposalMethod == GifDisposalMethod.RestoreToBackground || (source.LocalColorTable?.Length == 256 && !source.HasTransparency);

return new()
{
ColorTable = source.LocalColorTable,
ColorTableMode = source.ColorTableMode == GifColorTableMode.Global ? FrameColorTableMode.Global : FrameColorTableMode.Local,
Duration = TimeSpan.FromMilliseconds(source.FrameDelay * 10),
DisposalMode = GetMode(source.DisposalMethod),
BlendMode = source.DisposalMethod == GifDisposalMethod.RestoreToBackground ? FrameBlendMode.Source : FrameBlendMode.Over,
BlendMode = blendSource ? FrameBlendMode.Source : FrameBlendMode.Over,
};
}

private static FrameDisposalMode GetMode(GifDisposalMethod method) => method switch
{
Expand Down
4 changes: 2 additions & 2 deletions src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public Vp8LBitReader(IMemoryOwner<byte> data)
this.Eos = false;

ulong currentValue = 0;
System.Span<byte> dataSpan = this.Data.Memory.Span;
Span<byte> dataSpan = this.Data.Memory.Span;
for (int i = 0; i < 8; i++)
{
currentValue |= (ulong)dataSpan[i] << (8 * i);
Expand Down Expand Up @@ -103,7 +103,7 @@ public Vp8LBitReader(Stream inputStream, uint imageDataSize, MemoryAllocator mem
}

ulong currentValue = 0;
System.Span<byte> dataSpan = this.Data.Memory.Span;
Span<byte> dataSpan = this.Data.Memory.Span;
for (int i = 0; i < length; i++)
{
currentValue |= (ulong)dataSpan[i] << (8 * i);
Expand Down
4 changes: 3 additions & 1 deletion tests/ImageSharp.Tests/TestImages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ public static class Gif
public const string GlobalQuantizationTest = "Gif/GlobalQuantizationTest.gif";
public const string MixedDisposal = "Gif/mixed-disposal.gif";
public const string M4nb = "Gif/m4nb.gif";
public const string Bit18RGBCube = "Gif/18-bit_RGB_Cube.gif";

// Test images from https://github.com/robert-ancell/pygif/tree/master/test-suite
public const string ZeroSize = "Gif/image-zero-size.gif";
Expand Down Expand Up @@ -533,7 +534,8 @@ public static class Issues
Issues.Issue2450_A,
Issues.Issue2450_B,
Issues.BadDescriptorWidth,
Issues.Issue1530
Issues.Issue1530,
Bit18RGBCube
};
}

Expand Down
3 changes: 3 additions & 0 deletions tests/Images/Input/Gif/18-bit_RGB_Cube.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit cb07e36

Please sign in to comment.