Skip to content

Commit

Permalink
Merge pull request #2363 from IldarKhayrutdinov/tiff-frames-meta
Browse files Browse the repository at this point in the history
Support frames metadata for Identify
  • Loading branch information
JimBobSquarePants authored Mar 1, 2023
2 parents 215c609 + 24a0a5f commit 6ec1692
Show file tree
Hide file tree
Showing 34 changed files with 672 additions and 200 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,14 @@ jobs:

- name: Feedz Publish
shell: pwsh
run: dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.FEEDZ_TOKEN}} -s https://f.feedz.io/sixlabors/sixlabors/nuget/index.json -ss https://f.feedz.io/sixlabors/sixlabors/symbols --skip-duplicate
run: |
dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.FEEDZ_TOKEN}} -s https://f.feedz.io/sixlabors/sixlabors/nuget/index.json --skip-duplicate
dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.FEEDZ_TOKEN}} -s https://f.feedz.io/sixlabors/sixlabors/symbols --skip-duplicate
- name: NuGet Publish
if: ${{ startsWith(github.ref, 'refs/tags/') }}
shell: pwsh
run: dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.NUGET_TOKEN}} -s https://api.nuget.org/v3/index.json --skip-duplicate
run: |
dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.NUGET_TOKEN}} -s https://api.nuget.org/v3/index.json --skip-duplicate
dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.NUGET_TOKEN}} -s https://api.nuget.org/v3/index.json --skip-duplicate
2 changes: 1 addition & 1 deletion src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ReadImageHeaders(stream, out _, out _);
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata);
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), new(this.infoHeader.Width, this.infoHeader.Height), this.metadata);
}

/// <summary>
Expand Down
72 changes: 57 additions & 15 deletions src/ImageSharp/Formats/Gif/GifDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
/// <inheritdoc />
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
uint frameCount = 0;
ImageFrameMetadata? previousFrame = null;
List<ImageFrameMetadata> framesMetadata = new();
try
{
this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream);
Expand All @@ -182,14 +185,23 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
{
if (nextFlag == GifConstants.ImageLabel)
{
this.ReadImageDescriptor(stream);
if (previousFrame != null && ++frameCount == this.maxFrames)
{
break;
}

this.ReadFrameMetadata(stream, framesMetadata, ref previousFrame);

// Reset per-frame state.
this.imageDescriptor = default;
this.graphicsControlExtension = default;
}
else if (nextFlag == GifConstants.ExtensionIntroducer)
{
switch (stream.ReadByte())
{
case GifConstants.GraphicControlLabel:
SkipBlock(stream); // Skip graphic control extension block
this.ReadGraphicalControlExtension(stream);
break;
case GifConstants.CommentLabel:
this.ReadComments(stream);
Expand Down Expand Up @@ -226,9 +238,9 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat

return new ImageInfo(
new PixelTypeInfo(this.logicalScreenDescriptor.BitsPerPixel),
this.logicalScreenDescriptor.Width,
this.logicalScreenDescriptor.Height,
this.metadata);
new(this.logicalScreenDescriptor.Width, this.logicalScreenDescriptor.Height),
this.metadata,
framesMetadata);
}

/// <summary>
Expand Down Expand Up @@ -486,7 +498,7 @@ private void ReadFrameColors<TPixel>(ref Image<TPixel>? image, ref ImageFrame<TP
image = new Image<TPixel>(this.configuration, imageWidth, imageHeight, this.metadata);
}

this.SetFrameMetadata(image.Frames.RootFrame.Metadata, true);
this.SetFrameMetadata(image.Frames.RootFrame.Metadata);

imageFrame = image.Frames.RootFrame;
}
Expand All @@ -499,7 +511,7 @@ private void ReadFrameColors<TPixel>(ref Image<TPixel>? image, ref ImageFrame<TP

currentFrame = image!.Frames.CreateFrame();

this.SetFrameMetadata(currentFrame.Metadata, false);
this.SetFrameMetadata(currentFrame.Metadata);

imageFrame = currentFrame;

Expand Down Expand Up @@ -606,6 +618,37 @@ private void ReadFrameColors<TPixel>(ref Image<TPixel>? image, ref ImageFrame<TP
}
}

/// <summary>
/// Reads the frames metadata.
/// </summary>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="frameMetadata">The collection of frame metadata.</param>
/// <param name="previousFrame">The previous frame metadata.</param>
private void ReadFrameMetadata(BufferedReadStream stream, List<ImageFrameMetadata> frameMetadata, ref ImageFrameMetadata? previousFrame)
{
this.ReadImageDescriptor(stream);

// Skip the color table for this frame if local.
if (this.imageDescriptor.LocalColorTableFlag)
{
stream.Skip(this.imageDescriptor.LocalColorTableSize * 3);
}

// Skip the frame indices. Pixels length + mincode size.
// The gif format does not tell us the length of the compressed data beforehand.
int minCodeSize = stream.ReadByte();
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream);
lzwDecoder.SkipIndices(minCodeSize, this.imageDescriptor.Width * this.imageDescriptor.Height);

ImageFrameMetadata currentFrame = new();
frameMetadata.Add(currentFrame);
this.SetFrameMetadata(currentFrame);
previousFrame = currentFrame;

// Skip any remaining blocks
SkipBlock(stream);
}

/// <summary>
/// Restores the current frame area to the background.
/// </summary>
Expand All @@ -627,34 +670,33 @@ private void RestoreToBackground<TPixel>(ImageFrame<TPixel> frame)
}

/// <summary>
/// Sets the frames metadata.
/// Sets the metadata for the image frame.
/// </summary>
/// <param name="meta">The metadata.</param>
/// <param name="isRoot">Whether the metadata represents the root frame.</param>
/// <param name="metadata">The metadata.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetFrameMetadata(ImageFrameMetadata meta, bool isRoot)
private void SetFrameMetadata(ImageFrameMetadata metadata)
{
// Frames can either use the global table or their own local table.
if (isRoot && this.logicalScreenDescriptor.GlobalColorTableFlag
if (this.logicalScreenDescriptor.GlobalColorTableFlag
&& this.logicalScreenDescriptor.GlobalColorTableSize > 0)
{
GifFrameMetadata gifMeta = meta.GetGifMetadata();
GifFrameMetadata gifMeta = metadata.GetGifMetadata();
gifMeta.ColorTableMode = GifColorTableMode.Global;
gifMeta.ColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize;
}

if (this.imageDescriptor.LocalColorTableFlag
&& this.imageDescriptor.LocalColorTableSize > 0)
{
GifFrameMetadata gifMeta = meta.GetGifMetadata();
GifFrameMetadata gifMeta = metadata.GetGifMetadata();
gifMeta.ColorTableMode = GifColorTableMode.Local;
gifMeta.ColorTableLength = this.imageDescriptor.LocalColorTableSize;
}

// Graphics control extensions is optional.
if (this.graphicsControlExtension != default)
{
GifFrameMetadata gifMeta = meta.GetGifMetadata();
GifFrameMetadata gifMeta = metadata.GetGifMetadata();
gifMeta.FrameDelay = this.graphicsControlExtension.DelayTime;
gifMeta.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
}
Expand Down
155 changes: 154 additions & 1 deletion src/ImageSharp/Formats/Gif/LzwDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream)
}

/// <summary>
/// Decodes and decompresses all pixel indices from the stream.
/// Decodes and decompresses all pixel indices from the stream, assigning the pixel values to the buffer.
/// </summary>
/// <param name="minCodeSize">Minimum code size of the data.</param>
/// <param name="pixels">The pixel array to decode to.</param>
Expand Down Expand Up @@ -232,6 +232,159 @@ public void DecodePixels(int minCodeSize, Buffer2D<byte> pixels)
}
}

/// <summary>
/// Decodes and decompresses all pixel indices from the stream allowing skipping of the data.
/// </summary>
/// <param name="minCodeSize">Minimum code size of the data.</param>
/// <param name="length">The resulting index table length.</param>
public void SkipIndices(int minCodeSize, int length)
{
// 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.");
}

int codeSize = minCodeSize + 1;

// Calculate the end code
int endCode = clearCode + 1;

// Calculate the available code.
int availableCode = clearCode + 2;

// Jillzhangs Code see: http://giflib.codeplex.com/
// Adapted from John Cristy's ImageMagick.
int code;
int oldCode = NullCode;
int codeMask = (1 << codeSize) - 1;
int bits = 0;

int top = 0;
int count = 0;
int bi = 0;
int xyz = 0;

int data = 0;
int first = 0;

ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan());
ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan());
ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan());

for (code = 0; code < clearCode; code++)
{
Unsafe.Add(ref suffixRef, code) = (byte)code;
}

Span<byte> buffer = stackalloc byte[byte.MaxValue];
while (xyz < length)
{
if (top == 0)
{
if (bits < codeSize)
{
// Load bytes until there are enough bits for a code.
if (count == 0)
{
// Read a new data block.
count = this.ReadBlock(buffer);
if (count == 0)
{
break;
}

bi = 0;
}

data += buffer[bi] << bits;

bits += 8;
bi++;
count--;
continue;
}

// Get the next code
code = data & codeMask;
data >>= codeSize;
bits -= codeSize;

// Interpret the code
if (code > availableCode || code == endCode)
{
break;
}

if (code == clearCode)
{
// Reset the decoder
codeSize = minCodeSize + 1;
codeMask = (1 << codeSize) - 1;
availableCode = clearCode + 2;
oldCode = NullCode;
continue;
}

if (oldCode == NullCode)
{
Unsafe.Add(ref pixelStackRef, top++) = Unsafe.Add(ref suffixRef, code);
oldCode = code;
first = code;
continue;
}

int inCode = code;
if (code == availableCode)
{
Unsafe.Add(ref pixelStackRef, top++) = (byte)first;

code = oldCode;
}

while (code > clearCode)
{
Unsafe.Add(ref pixelStackRef, top++) = Unsafe.Add(ref suffixRef, code);
code = Unsafe.Add(ref prefixRef, code);
}

int suffixCode = Unsafe.Add(ref suffixRef, code);
first = suffixCode;
Unsafe.Add(ref pixelStackRef, top++) = suffixCode;

// Fix for Gifs that have "deferred clear code" as per here :
// https://bugzilla.mozilla.org/show_bug.cgi?id=55918
if (availableCode < MaxStackSize)
{
Unsafe.Add(ref prefixRef, availableCode) = oldCode;
Unsafe.Add(ref suffixRef, availableCode) = first;
availableCode++;
if (availableCode == codeMask + 1 && availableCode < MaxStackSize)
{
codeSize++;
codeMask = (1 << codeSize) - 1;
}
}

oldCode = inCode;
}

// Pop a pixel off the pixel stack.
top--;

// Clear missing pixels
xyz++;
}
}

/// <summary>
/// Reads the next data block from the stream. A data block begins with a byte,
/// which defines the size of the block, followed by the block itself.
Expand Down
2 changes: 1 addition & 1 deletion src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
this.InitDerivedMetadataProperties();

Size pixelSize = this.Frame.PixelSize;
return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata);
return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), new(pixelSize.Width, pixelSize.Height), this.Metadata);
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat

// BlackAndWhite pixels are encoded into a byte.
int bitsPerPixel = this.componentType == PbmComponentType.Short ? 16 : 8;
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.pixelSize.Width, this.pixelSize.Height, this.metadata);
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), new(this.pixelSize.Width, this.pixelSize.Height), this.metadata);
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/ImageSharp/Formats/Png/PngDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
PngThrowHelper.ThrowNoHeader();
}

return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata);
return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), new(this.header.Width, this.header.Height), metadata);
}
finally
{
Expand Down
3 changes: 1 addition & 2 deletions src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -658,8 +658,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
this.ReadFileHeader(stream);
return new ImageInfo(
new PixelTypeInfo(this.fileHeader.PixelDepth),
this.fileHeader.Width,
this.fileHeader.Height,
new(this.fileHeader.Width, this.fileHeader.Height),
this.metadata);
}

Expand Down
Loading

0 comments on commit 6ec1692

Please sign in to comment.