Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
114 changes: 107 additions & 7 deletions src/ImageSharp/Formats/Png/PngDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,137 @@ public sealed class PngDecoder : IImageDecoder, IPngDecoderOptions, IImageInfoDe
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoderCore(configuration, this);
PngDecoderCore decoder = new(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
}

/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
public Image Decode(Configuration configuration, Stream stream)
{
PngDecoderCore decoder = new(configuration, true);
IImageInfo info = decoder.Identify(configuration, stream);
stream.Position = 0;

PngMetadata meta = info.Metadata.GetPngMetadata();
PngColorType color = meta.ColorType.GetValueOrDefault();
PngBitDepth bits = meta.BitDepth.GetValueOrDefault();
switch (color)
{
case PngColorType.Grayscale:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? this.Decode<L16>(configuration, stream)
: this.Decode<La32>(configuration, stream);
}

return !meta.HasTransparency
? this.Decode<L8>(configuration, stream)
: this.Decode<La16>(configuration, stream);

case PngColorType.Rgb:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? this.Decode<Rgb48>(configuration, stream)
: this.Decode<Rgba64>(configuration, stream);
}

return !meta.HasTransparency
? this.Decode<Rgb24>(configuration, stream)
: this.Decode<Rgba32>(configuration, stream);

case PngColorType.Palette:
return this.Decode<Rgba32>(configuration, stream);

case PngColorType.GrayscaleWithAlpha:
return (bits == PngBitDepth.Bit16)
? this.Decode<La32>(configuration, stream)
: this.Decode<La16>(configuration, stream);

case PngColorType.RgbWithAlpha:
return (bits == PngBitDepth.Bit16)
? this.Decode<Rgba64>(configuration, stream)
: this.Decode<Rgba32>(configuration, stream);

default:
return this.Decode<Rgba32>(configuration, stream);
}
}

/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoderCore(configuration, this);
PngDecoderCore decoder = new(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}

/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
{
PngDecoderCore decoder = new(configuration, true);
IImageInfo info = await decoder.IdentifyAsync(configuration, stream, cancellationToken).ConfigureAwait(false);
stream.Position = 0;

PngMetadata meta = info.Metadata.GetPngMetadata();
PngColorType color = meta.ColorType.GetValueOrDefault();
PngBitDepth bits = meta.BitDepth.GetValueOrDefault();
switch (color)
{
case PngColorType.Grayscale:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? await this.DecodeAsync<L16>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<La32>(configuration, stream, cancellationToken).ConfigureAwait(false);
}

return !meta.HasTransparency
? await this.DecodeAsync<L8>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<La16>(configuration, stream, cancellationToken).ConfigureAwait(false);

case PngColorType.Rgb:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? await this.DecodeAsync<Rgb48>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<Rgba64>(configuration, stream, cancellationToken).ConfigureAwait(false);
}

return !meta.HasTransparency
? await this.DecodeAsync<Rgb24>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);

case PngColorType.Palette:
return await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);

case PngColorType.GrayscaleWithAlpha:
return (bits == PngBitDepth.Bit16)
? await this.DecodeAsync<La32>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<La16>(configuration, stream, cancellationToken).ConfigureAwait(false);

case PngColorType.RgbWithAlpha:
return (bits == PngBitDepth.Bit16)
? await this.DecodeAsync<Rgba64>(configuration, stream, cancellationToken).ConfigureAwait(false)
: await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);

default:
return await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);
}
}

/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
var decoder = new PngDecoderCore(configuration, this);
PngDecoderCore decoder = new(configuration, this);
return decoder.Identify(configuration, stream);
}

/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
var decoder = new PngDecoderCore(configuration, this);
PngDecoderCore decoder = new(configuration, this);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
}
}
Expand Down
48 changes: 29 additions & 19 deletions src/ImageSharp/Formats/Png/PngDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ public PngDecoderCore(Configuration configuration, IPngDecoderOptions options)
this.ignoreMetadata = options.IgnoreMetadata;
}

internal PngDecoderCore(Configuration configuration, bool ignoreMetadata)
{
this.Configuration = configuration ?? Configuration.Default;
this.memoryAllocator = this.Configuration.MemoryAllocator;
this.ignoreMetadata = ignoreMetadata;
}

/// <inheritdoc/>
public Configuration Configuration { get; }

Expand Down Expand Up @@ -168,12 +175,12 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken

break;
case PngChunkType.Palette:
var pal = new byte[chunk.Length];
byte[] pal = new byte[chunk.Length];
chunk.Data.GetSpan().CopyTo(pal);
this.palette = pal;
break;
case PngChunkType.Transparency:
var alpha = new byte[chunk.Length];
byte[] alpha = new byte[chunk.Length];
chunk.Data.GetSpan().CopyTo(alpha);
this.paletteAlpha = alpha;
this.AssignTransparentMarkers(alpha, pngMetadata);
Expand All @@ -190,7 +197,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
case PngChunkType.Exif:
if (!this.ignoreMetadata)
{
var exifData = new byte[chunk.Length];
byte[] exifData = new byte[chunk.Length];
chunk.Data.GetSpan().CopyTo(exifData);
metadata.ExifProfile = new ExifProfile(exifData);
}
Expand Down Expand Up @@ -251,6 +258,12 @@ public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancella
case PngChunkType.Data:
this.SkipChunkDataAndCrc(chunk);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spec says:

When present, the tRNS chunk must precede the first IDAT chunk, and must follow the PLTE chunk, if any.

It means that if we reach Data, we are free to quit the loop. Forwarding the file position beyond the data chunk may have IO overhead we can easily avoid here.

break;
case PngChunkType.Transparency:
byte[] alpha = new byte[chunk.Length];
chunk.Data.GetSpan().CopyTo(alpha);
this.paletteAlpha = alpha;
this.AssignTransparentMarkers(alpha, pngMetadata);
break;
case PngChunkType.Text:
this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan());
break;
Expand All @@ -263,7 +276,7 @@ public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancella
case PngChunkType.Exif:
if (!this.ignoreMetadata)
{
var exifData = new byte[chunk.Length];
byte[] exifData = new byte[chunk.Length];
chunk.Data.GetSpan().CopyTo(exifData);
metadata.ExifProfile = new ExifProfile(exifData);
}
Expand Down Expand Up @@ -364,11 +377,10 @@ 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)
{

// 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) / 100_000F;
}
=> pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) * 1e-5F;

/// <summary>
/// Initializes the image and various buffers needed for processing
Expand Down Expand Up @@ -477,19 +489,17 @@ private int CalculateScanlineLength(int width)
private void ReadScanlines<TPixel>(PngChunk chunk, ImageFrame<TPixel> image, PngMetadata pngMetadata)
where TPixel : unmanaged, IPixel<TPixel>
{
using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk))
{
deframeStream.AllocateNewBytes(chunk.Length, true);
DeflateStream dataStream = deframeStream.CompressedStream;
using var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk);
deframeStream.AllocateNewBytes(chunk.Length, true);
DeflateStream dataStream = deframeStream.CompressedStream;

if (this.header.InterlaceMethod == PngInterlaceMode.Adam7)
{
this.DecodeInterlacedPixelData(dataStream, image, pngMetadata);
}
else
{
this.DecodePixelData(dataStream, image, pngMetadata);
}
if (this.header.InterlaceMethod == PngInterlaceMode.Adam7)
{
this.DecodeInterlacedPixelData(dataStream, image, pngMetadata);
}
else
{
this.DecodePixelData(dataStream, image, pngMetadata);
}
}

Expand Down
Loading