Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8db4828
Handle empty XMP profiles. Fix #2012
JimBobSquarePants Feb 18, 2022
5214681
Add ARM version of adler32
brianpopow Feb 18, 2022
ce429e9
Remove serial computation, does not not make sense here
brianpopow Feb 18, 2022
c7b62d8
Fix build error
brianpopow Feb 18, 2022
36420de
Block size is the same for sse and arm
brianpopow Feb 18, 2022
c64078c
Remove unnecessary throw and optimize write.
JimBobSquarePants Feb 19, 2022
c129278
Don't throw for bad min code, just don't decode indices.
JimBobSquarePants Feb 19, 2022
892dbe3
Throw exception, if palette chunk is missing
brianpopow Feb 19, 2022
d98171f
Add tests for missing palette chunk
brianpopow Feb 19, 2022
bcb3035
Merge branch 'main' into bp/missingpalette
brianpopow Feb 19, 2022
d7fec18
Apply suggestions from code review
brianpopow Feb 19, 2022
cfc7847
Use MinBufferSize for ARM
brianpopow Feb 18, 2022
29ddc60
Change error message to "...a palette chunk"
brianpopow Feb 19, 2022
bef5162
Add missing using System.Runtime.InteropServices;
brianpopow Feb 19, 2022
d62391e
Fix bug in storing the results
brianpopow Feb 20, 2022
c3c14e8
Add adler tests with and without intrinsics
brianpopow Feb 20, 2022
9179c11
Add common methods for handling left over for sse and arm
brianpopow Feb 20, 2022
78e5b6c
Throw for corrupt LZW min code. Add test for deferred clear code
JimBobSquarePants Feb 21, 2022
968c5ff
Merge pull request #2020 from SixLabors/bp/missingpalette
JimBobSquarePants Feb 21, 2022
eda0906
Merge branch 'main' into js/fix-2012
JimBobSquarePants Feb 21, 2022
18f7c09
Merge branch 'main' into bp/adlerarm
brianpopow Feb 21, 2022
85a0ac6
Use GifThrowHelper
JimBobSquarePants Feb 21, 2022
6727d6e
Merge pull request #2014 from SixLabors/js/fix-2012
JimBobSquarePants Feb 21, 2022
97d1a4e
Merge remote-tracking branch 'origin/main' into bp/invalidgamma
brianpopow Feb 21, 2022
0fa6085
Throw exception, if gamma chunk does not contain enough data
brianpopow Feb 21, 2022
d76c40a
Ignore invalid gamma chunks
brianpopow Feb 21, 2022
c059656
Merge pull request #2021 from SixLabors/bp/invalidgamma
brianpopow Feb 21, 2022
854ea5d
workaround for #2001 / https://github.com/dotnet/runtime/issues/65466
antonfirsov Feb 21, 2022
5b82c57
Add compiler directives
JimBobSquarePants Feb 22, 2022
de5f661
make GetTotalAvailableMemoryBytes conditional
antonfirsov Feb 22, 2022
7387607
Merge branch 'af/TotalAvailableMemoryBytes-workaround' of https://git…
antonfirsov Feb 22, 2022
3259c94
Merge pull request #2025 from SixLabors/af/TotalAvailableMemoryBytes-…
antonfirsov Feb 22, 2022
bfd7b54
Merge branch 'main' into bp/adlerarm
brianpopow Feb 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ImageSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gif", "Gif", "{EE3FB0B3-1C3
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{BF8DFDC1-CEE5-4A37-B216-D3085360C776}"
ProjectSection(SolutionItems) = preProject
tests\Images\Input\Gif\issues\bugzilla-55918.gif = tests\Images\Input\Gif\issues\bugzilla-55918.gif
tests\Images\Input\Gif\issues\issue1505_argumentoutofrange.png = tests\Images\Input\Gif\issues\issue1505_argumentoutofrange.png
tests\Images\Input\Gif\issues\issue1530.gif = tests\Images\Input\Gif\issues\issue1530.gif
tests\Images\Input\Gif\issues\issue1668_invalidcolorindex.gif = tests\Images\Input\Gif\issues\issue1668_invalidcolorindex.gif
tests\Images\Input\Gif\issues\issue1962_tiniest_gif_1st.gif = tests\Images\Input\Gif\issues\issue1962_tiniest_gif_1st.gif
tests\Images\Input\Gif\issues\issue2012_drona1.gif = tests\Images\Input\Gif\issues\issue2012_drona1.gif
tests\Images\Input\Gif\issues\issue2012_Stronghold-Crusader-Extreme-Cover.gif = tests\Images\Input\Gif\issues\issue2012_Stronghold-Crusader-Extreme-Cover.gif
tests\Images\Input\Gif\issues\issue403_baddescriptorwidth.gif = tests\Images\Input\Gif\issues\issue403_baddescriptorwidth.gif
tests\Images\Input\Gif\issues\issue405_badappextlength252-2.gif = tests\Images\Input\Gif\issues\issue405_badappextlength252-2.gif
tests\Images\Input\Gif\issues\issue405_badappextlength252.gif = tests\Images\Input\Gif\issues\issue405_badappextlength252.gif
Expand Down
2 changes: 1 addition & 1 deletion src/ImageSharp/Common/Extensions/StreamExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp
internal static class StreamExtensions
{
/// <summary>
/// Writes data from a stream into the provided buffer.
/// Writes data from a stream from the provided buffer.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="buffer">The buffer.</param>
Expand Down
16 changes: 10 additions & 6 deletions src/ImageSharp/Formats/Gif/GifDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -265,10 +265,14 @@ private void ReadApplicationExtension()
this.stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize);
bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes);

if (isXmp)
if (isXmp && !this.IgnoreMetadata)
{
var extension = GifXmpApplicationExtension.Read(this.stream);
this.metadata.XmpProfile = new XmpProfile(extension.Data);
var extension = GifXmpApplicationExtension.Read(this.stream, this.MemoryAllocator);
if (extension.Data.Length > 0)
{
this.metadata.XmpProfile = new XmpProfile(extension.Data);
}

return;
}
else
Expand Down Expand Up @@ -374,8 +378,8 @@ private void ReadFrame<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> p
}

indices = this.Configuration.MemoryAllocator.Allocate2D<byte>(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean);

this.ReadFrameIndices(indices);

Span<byte> rawColorTable = default;
if (localColorTable != null)
{
Expand Down Expand Up @@ -406,9 +410,9 @@ private void ReadFrame<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> p
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadFrameIndices(Buffer2D<byte> indices)
{
int dataSize = this.stream.ReadByte();
int minCodeSize = this.stream.ReadByte();
using var lzwDecoder = new LzwDecoder(this.Configuration.MemoryAllocator, this.stream);
lzwDecoder.DecodePixels(dataSize, indices);
lzwDecoder.DecodePixels(minCodeSize, indices);
}

/// <summary>
Expand Down
27 changes: 16 additions & 11 deletions src/ImageSharp/Formats/Gif/GifEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
this.WriteComments(gifMetadata, stream);

// Write application extensions.
this.WriteApplicationExtensions(stream, image.Frames.Count, gifMetadata.RepeatCount, metadata.XmpProfile);
XmpProfile xmpProfile = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile;
this.WriteApplicationExtensions(stream, image.Frames.Count, gifMetadata.RepeatCount, xmpProfile);

if (useGlobalTable)
{
Expand All @@ -137,7 +138,6 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
// Clean up.
quantized.Dispose();

// TODO: Write extension etc
stream.WriteByte(GifConstants.EndIntroducer);
}

Expand Down Expand Up @@ -428,26 +428,31 @@ private void WriteExtension<TGifExtension>(TGifExtension extension, Stream strea
where TGifExtension : struct, IGifExtension
{
IMemoryOwner<byte> owner = null;
Span<byte> buffer;
Span<byte> extensionBuffer;
int extensionSize = extension.ContentLength;
if (extensionSize > this.buffer.Length - 3)

if (extensionSize == 0)
{
return;
}
else if (extensionSize > this.buffer.Length - 3)
{
owner = this.memoryAllocator.Allocate<byte>(extensionSize + 3);
buffer = owner.GetSpan();
extensionBuffer = owner.GetSpan();
}
else
{
buffer = this.buffer;
extensionBuffer = this.buffer;
}

buffer[0] = GifConstants.ExtensionIntroducer;
buffer[1] = extension.Label;
extensionBuffer[0] = GifConstants.ExtensionIntroducer;
extensionBuffer[1] = extension.Label;

extension.WriteTo(buffer.Slice(2));
extension.WriteTo(extensionBuffer.Slice(2));

buffer[extensionSize + 2] = GifConstants.Terminator;
extensionBuffer[extensionSize + 2] = GifConstants.Terminator;

stream.Write(buffer, 0, extensionSize + 3);
stream.Write(extensionBuffer, 0, extensionSize + 3);
owner?.Dispose();
}

Expand Down
25 changes: 17 additions & 8 deletions src/ImageSharp/Formats/Gif/LzwDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,21 +64,30 @@ public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream)
/// <summary>
/// Decodes and decompresses all pixel indices from the stream.
/// </summary>
/// <param name="dataSize">Size of the data.</param>
/// <param name="minCodeSize">Minimum code size of the data.</param>
/// <param name="pixels">The pixel array to decode to.</param>
public void DecodePixels(int dataSize, Buffer2D<byte> pixels)
public void DecodePixels(int minCodeSize, Buffer2D<byte> pixels)
{
Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize));
// Calculate the clear code. The value of the clear code is 2 ^ minCodeSize
int clearCode = 1 << minCodeSize;

// It is possible to specify a larger LZW minimum code size than the palette length in bits
// which may leave a gap in the codes where no colors are assigned.
// http://www.matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp#lzw_compression
if (minCodeSize < 2 || clearCode > MaxStackSize)
{
// Don't attempt to decode the frame indices.
// Theoretically we could determine a min code size from the length of the provided
// color palette but we won't bother since the image is most likely corrupted.
GifThrowHelper.ThrowInvalidImageContentException("Gif Image does not contain a valid LZW minimum code.");
}

// The resulting index table length.
int width = pixels.Width;
int height = pixels.Height;
int length = width * height;

// Calculate the clear code. The value of the clear code is 2 ^ dataSize
int clearCode = 1 << dataSize;

int codeSize = dataSize + 1;
int codeSize = minCodeSize + 1;

// Calculate the end code
int endCode = clearCode + 1;
Expand Down Expand Up @@ -165,7 +174,7 @@ public void DecodePixels(int dataSize, Buffer2D<byte> pixels)
if (code == clearCode)
{
// Reset the decoder
codeSize = dataSize + 1;
codeSize = minCodeSize + 1;
codeMask = (1 << codeSize) - 1;
availableCode = clearCode + 2;
oldCode = NullCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,11 @@ public int WriteTo(Span<byte> buffer)

dest = this;

return 5;
return ((IGifExtension)this).ContentLength;
}

public static GifGraphicControlExtension Parse(ReadOnlySpan<byte> buffer)
{
return MemoryMarshal.Cast<byte, GifGraphicControlExtension>(buffer)[0];
}
=> MemoryMarshal.Cast<byte, GifGraphicControlExtension>(buffer)[0];

public static byte GetPackedValue(GifDisposalMethod disposalMethod, bool userInputFlag = false, bool transparencyFlag = false)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public int WriteTo(Span<byte> buffer)
// 0 means loop indefinitely. Count is set as play n + 1 times.
BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(14, 2), this.RepeatCount);

return 16; // Length - Introducer + Label + Terminator.
return this.ContentLength; // Length - Introducer + Label + Terminator.
}
}
}
75 changes: 38 additions & 37 deletions src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
// Licensed under the Apache License, Version 2.0.

using System;
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;

namespace SixLabors.ImageSharp.Formats.Gif
{
Expand All @@ -14,7 +14,10 @@ namespace SixLabors.ImageSharp.Formats.Gif

public byte Label => GifConstants.ApplicationExtensionLabel;

public int ContentLength => this.Data.Length + 269; // 12 + Data Length + 1 + 256
// size : 1
// identifier : 11
// magic trailer : 257
public int ContentLength => (this.Data.Length > 0) ? this.Data.Length + 269 : 0;

/// <summary>
/// Gets the raw Data.
Expand All @@ -25,51 +28,28 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Reads the XMP metadata from the specified stream.
/// </summary>
/// <param name="stream">The stream to read from.</param>
/// <param name="allocator">The memory allocator.</param>
/// <returns>The XMP metadata</returns>
/// <exception cref="ImageFormatException">Thrown if the XMP block is not properly terminated.</exception>
public static GifXmpApplicationExtension Read(Stream stream)
public static GifXmpApplicationExtension Read(Stream stream, MemoryAllocator allocator)
{
// Read data in blocks, until an \0 character is encountered.
// We overshoot, indicated by the terminatorIndex variable.
const int bufferSize = 256;
var list = new List<byte[]>();
int terminationIndex = -1;
while (terminationIndex < 0)
{
byte[] temp = new byte[bufferSize];
int bytesRead = stream.Read(temp);
list.Add(temp);
terminationIndex = Array.IndexOf(temp, (byte)1);
}
byte[] xmpBytes = ReadXmpData(stream, allocator);

// Pack all the blocks (except magic trailer) into one single array again.
int dataSize = ((list.Count - 1) * bufferSize) + terminationIndex;
byte[] buffer = new byte[dataSize];
Span<byte> bufferSpan = buffer;
int pos = 0;
for (int j = 0; j < list.Count - 1; j++)
// Exclude the "magic trailer", see XMP Specification Part 3, 1.1.2 GIF
int xmpLength = xmpBytes.Length - 256; // 257 - unread 0x0
byte[] buffer = Array.Empty<byte>();
if (xmpLength > 0)
{
list[j].CopyTo(bufferSpan.Slice(pos));
pos += bufferSize;
buffer = new byte[xmpLength];
xmpBytes.AsSpan(0, xmpLength).CopyTo(buffer);
stream.Skip(1); // Skip the terminator.
}

// Last one only needs the portion until terminationIndex copied over.
Span<byte> lastBytes = list[list.Count - 1];
lastBytes.Slice(0, terminationIndex).CopyTo(bufferSpan.Slice(pos));

// Skip the remainder of the magic trailer.
stream.Skip(258 - (bufferSize - terminationIndex));
return new GifXmpApplicationExtension(buffer);
}

public int WriteTo(Span<byte> buffer)
{
int totalSize = this.ContentLength;
if (buffer.Length < totalSize)
{
throw new InsufficientMemoryException("Unable to write XMP metadata to GIF image");
}

int bytesWritten = 0;
buffer[bytesWritten++] = GifConstants.ApplicationBlockSize;

Expand All @@ -91,7 +71,28 @@ public int WriteTo(Span<byte> buffer)

buffer[bytesWritten++] = 0x00;

return totalSize;
return this.ContentLength;
}

private static byte[] ReadXmpData(Stream stream, MemoryAllocator allocator)
{
using ChunkedMemoryStream bytes = new(allocator);

// XMP data doesn't have a fixed length nor is there an indicator of the length.
// So we simply read one byte at a time until we hit the 0x0 value at the end
// of the magic trailer or the end of the stream.
// Using ChunkedMemoryStream reduces the array resize allocation normally associated
// with writing from a non fixed-size buffer.
while (true)
{
int b = stream.ReadByte();
if (b <= 0)
{
return bytes.ToArray();
}

bytes.WriteByte((byte)b);
}
}
}
}
11 changes: 9 additions & 2 deletions src/ImageSharp/Formats/Png/PngDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,17 @@ private void ReadPhysicalChunk(ImageMetadata metadata, ReadOnlySpan<byte> data)
/// <param name="pngMetadata">The metadata to read to.</param>
/// <param name="data">The data containing physical data.</param>
private void ReadGammaChunk(PngMetadata pngMetadata, ReadOnlySpan<byte> data)
{
if (data.Length < 4)
{
// Ignore invalid gamma chunks.
return;
}

// The value is encoded as a 4-byte unsigned integer, representing gamma times 100000.
// For example, a gamma of 1/2.2 would be stored as 45455.
=> pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) * 1e-5F;
// The value is encoded as a 4-byte unsigned integer, representing gamma times 100000.
pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) * 1e-5F;
}

/// <summary>
/// Initializes the image and various buffers needed for processing
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using SixLabors.ImageSharp.Memory.Internals;

namespace SixLabors.ImageSharp.Memory
Expand All @@ -22,7 +21,7 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato
private readonly int poolCapacity;
private readonly UniformUnmanagedMemoryPool.TrimSettings trimSettings;

private UniformUnmanagedMemoryPool pool;
private readonly UniformUnmanagedMemoryPool pool;
private readonly UnmanagedMemoryAllocator nonPoolAllocator;

public UniformUnmanagedMemoryPoolMemoryAllocator(int? maxPoolSizeMegabytes)
Expand Down Expand Up @@ -74,6 +73,12 @@ internal UniformUnmanagedMemoryPoolMemoryAllocator(
this.nonPoolAllocator = new UnmanagedMemoryAllocator(unmanagedBufferSizeInBytes);
}

#if NETCOREAPP3_1_OR_GREATER
// This delegate allows overriding the method returning the available system memory,
// so we can test our workaround for https://github.com/dotnet/runtime/issues/65466
internal static Func<long> GetTotalAvailableMemoryBytes { get; set; } = () => GC.GetGCMemoryInfo().TotalAvailableMemoryBytes;
#endif

/// <inheritdoc />
protected internal override int GetBufferCapacityInBytes() => this.poolBufferSizeInBytes;

Expand Down Expand Up @@ -152,8 +157,13 @@ private static long GetDefaultMaxPoolSizeBytes()
// https://github.com/dotnet/runtime/issues/55126#issuecomment-876779327
if (Environment.Is64BitProcess || !RuntimeInformation.FrameworkDescription.StartsWith(".NET 5.0"))
{
GCMemoryInfo info = GC.GetGCMemoryInfo();
return info.TotalAvailableMemoryBytes / 8;
long total = GetTotalAvailableMemoryBytes();

// Workaround for https://github.com/dotnet/runtime/issues/65466
if (total > 0)
{
return total / 8;
}
}
#endif

Expand Down
3 changes: 2 additions & 1 deletion src/ImageSharp/Metadata/ImageMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ private ImageMetadata(ImageMetadata other)
this.ExifProfile = other.ExifProfile?.DeepClone();
this.IccProfile = other.IccProfile?.DeepClone();
this.IptcProfile = other.IptcProfile?.DeepClone();
this.XmpProfile = other.XmpProfile?.DeepClone();
}

/// <summary>
Expand Down Expand Up @@ -175,7 +176,7 @@ public TFormatMetadata GetFormatMetadata<TFormatMetadata>(IImageFormat<TFormatMe
}

/// <inheritdoc/>
public ImageMetadata DeepClone() => new ImageMetadata(this);
public ImageMetadata DeepClone() => new(this);

/// <summary>
/// Synchronizes the profiles with the current metadata.
Expand Down
Loading