Browse Source

Eliminate string allocations for PngChunkType

af/merge-core
Jason Nelson 8 years ago
parent
commit
c089c8cffc
  1. 15
      src/ImageSharp/Formats/Png/PngChunk.cs
  2. 17
      src/ImageSharp/Formats/Png/PngChunkType.cs
  3. 4
      src/ImageSharp/Formats/Png/PngChunkTypeNames.cs
  4. 47
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  5. 36
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  6. 12
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

15
src/ImageSharp/Formats/Png/PngChunk.cs

@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
internal readonly struct PngChunk
{
public PngChunk(int length, string type, IManagedByteBuffer data = null, uint crc = default)
public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null, uint crc = default)
{
this.Length = length;
this.Type = type;
@ -27,9 +27,10 @@ namespace SixLabors.ImageSharp.Formats.Png
public int Length { get; }
/// <summary>
/// Gets the chunk type as string with 4 chars.
/// Gets the chunk type.
/// The chunk type value the UInt32BigEndian encoding of its 4 ASCII characters.
/// </summary>
public string Type { get; }
public PngChunkType Type { get; }
/// <summary>
/// Gets the data bytes appropriate to the chunk type, if any.
@ -48,9 +49,9 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets a value indicating whether the given chunk is critical to decoding
/// </summary>
public bool IsCritical =>
this.Type == PngChunkTypes.Header ||
this.Type == PngChunkTypes.Palette ||
this.Type == PngChunkTypes.Data ||
this.Type == PngChunkTypes.End;
this.Type == PngChunkType.Header ||
this.Type == PngChunkType.Palette ||
this.Type == PngChunkType.Data ||
this.Type == PngChunkType.End;
}
}

17
src/ImageSharp/Formats/Png/PngChunkType.cs

@ -0,0 +1,17 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Png
{
internal enum PngChunkType : uint
{
Header = 1229472850U, // IHDR
Palette = 1347179589U, // PLTE
Data = 1229209940U, // IDAT
End = 1229278788U, // IEND
PaletteAlpha = 1951551059U, // tRNS
Text = 1950701684U, // tEXt
Gamma = 1732332865U, // gAMA
Physical = 1883789683U, // pHYs
}
}

4
src/ImageSharp/Formats/Png/PngChunkTypes.cs → src/ImageSharp/Formats/Png/PngChunkTypeNames.cs

@ -4,9 +4,9 @@
namespace SixLabors.ImageSharp.Formats.Png
{
/// <summary>
/// Contains a list of possible chunk type identifiers.
/// Contains a list of possible chunk type identifier names.
/// </summary>
internal static class PngChunkTypes
internal static class PngChunkTypeNames
{
/// <summary>
/// The first chunk in a png file. Can only exists once. Contains

47
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -71,11 +71,6 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
private readonly byte[] crcBuffer = new byte[4];
/// <summary>
/// Reusable buffer for reading char arrays.
/// </summary>
private readonly char[] chars = new char[4];
/// <summary>
/// Reusable crc for validating chunks.
/// </summary>
@ -224,14 +219,14 @@ namespace SixLabors.ImageSharp.Formats.Png
{
switch (chunk.Type)
{
case PngChunkTypes.Header:
case PngChunkType.Header:
this.ReadHeaderChunk(chunk.Data.Array);
this.ValidateHeader();
break;
case PngChunkTypes.Physical:
case PngChunkType.Physical:
this.ReadPhysicalChunk(metadata, chunk.Data.Array);
break;
case PngChunkTypes.Data:
case PngChunkType.Data:
if (image == null)
{
this.InitializeImage(metadata, out image);
@ -241,21 +236,21 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ReadScanlines(deframeStream.CompressedStream, image.Frames.RootFrame);
this.currentStream.Read(this.crcBuffer, 0, 4);
break;
case PngChunkTypes.Palette:
case PngChunkType.Palette:
byte[] pal = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length);
this.palette = pal;
break;
case PngChunkTypes.PaletteAlpha:
case PngChunkType.PaletteAlpha:
byte[] alpha = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length);
this.paletteAlpha = alpha;
this.AssignTransparentMarkers(alpha);
break;
case PngChunkTypes.Text:
case PngChunkType.Text:
this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length);
break;
case PngChunkTypes.End:
case PngChunkType.End:
this.isEndChunkReached = true;
break;
}
@ -298,20 +293,20 @@ namespace SixLabors.ImageSharp.Formats.Png
{
switch (chunk.Type)
{
case PngChunkTypes.Header:
case PngChunkType.Header:
this.ReadHeaderChunk(chunk.Data.Array);
this.ValidateHeader();
break;
case PngChunkTypes.Physical:
case PngChunkType.Physical:
this.ReadPhysicalChunk(metadata, chunk.Data.Array);
break;
case PngChunkTypes.Data:
case PngChunkType.Data:
this.SkipChunkDataAndCrc(chunk);
break;
case PngChunkTypes.Text:
case PngChunkType.Text:
this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length);
break;
case PngChunkTypes.End:
case PngChunkType.End:
this.isEndChunkReached = true;
break;
}
@ -1214,10 +1209,10 @@ namespace SixLabors.ImageSharp.Formats.Png
}
}
string type = this.ReadChunkType();
PngChunkType type = this.ReadChunkType();
// NOTE: Reading the chunk data is the responsible of the caller
if (type == PngChunkTypes.Data)
if (type == PngChunkType.Data)
{
chunk = new PngChunk(length, type);
@ -1246,7 +1241,9 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.crc.Value != chunk.Crc)
{
throw new ImageFormatException($"CRC Error. PNG {chunk.Type} chunk is corrupt!");
string chunkName = Encoding.UTF8.GetString(this.chunkTypeBuffer, 0, 4);
throw new ImageFormatException($"CRC Error. PNG {chunkName} chunk is corrupt!");
}
}
@ -1297,20 +1294,16 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
private string ReadChunkType()
private PngChunkType ReadChunkType()
{
int numBytes = this.currentStream.Read(this.chunkTypeBuffer, 0, 4);
if (numBytes >= 1 && numBytes <= 3)
{
throw new ImageFormatException("Image stream is not valid!");
}
this.chars[0] = (char)this.chunkTypeBuffer[0];
this.chars[1] = (char)this.chunkTypeBuffer[1];
this.chars[2] = (char)this.chunkTypeBuffer[2];
this.chars[3] = (char)this.chunkTypeBuffer[3];
return new string(this.chars);
return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.chunkTypeBuffer.AsSpan());
}
/// <summary>

36
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -27,9 +27,9 @@ namespace SixLabors.ImageSharp.Formats.Png
private const int MaxBlockSize = 65535;
/// <summary>
/// Reusable buffer for writing chunk types.
/// Reusable buffer for writing general data.
/// </summary>
private readonly byte[] chunkTypeBuffer = new byte[4];
private readonly byte[] buffer = new byte[8];
/// <summary>
/// Reusable buffer for writing chunk data.
@ -423,7 +423,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.chunkDataBuffer[11] = header.FilterMethod;
this.chunkDataBuffer[12] = (byte)header.InterlaceMethod;
this.WriteChunk(stream, PngChunkTypes.Header, this.chunkDataBuffer, 0, 13);
this.WriteChunk(stream, PngChunkType.Header, this.chunkDataBuffer, 0, 13);
}
/// <summary>
@ -474,12 +474,12 @@ namespace SixLabors.ImageSharp.Formats.Png
}
}
this.WriteChunk(stream, PngChunkTypes.Palette, colorTable.Array, 0, colorTableLength);
this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength);
// Write the transparency data
if (anyAlpha)
{
this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable.Array, 0, pixelCount);
this.WriteChunk(stream, PngChunkType.PaletteAlpha, alphaTable.Array, 0, pixelCount);
}
}
}
@ -504,7 +504,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.chunkDataBuffer[8] = 1;
this.WriteChunk(stream, PngChunkTypes.Physical, this.chunkDataBuffer, 0, 9);
this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9);
}
}
@ -521,7 +521,7 @@ namespace SixLabors.ImageSharp.Formats.Png
BinaryPrimitives.WriteUInt32BigEndian(this.chunkDataBuffer.AsSpan(0, 4), gammaValue);
this.WriteChunk(stream, PngChunkTypes.Gamma, this.chunkDataBuffer, 0, 4);
this.WriteChunk(stream, PngChunkType.Gamma, this.chunkDataBuffer, 0, 4);
}
}
@ -589,7 +589,7 @@ namespace SixLabors.ImageSharp.Formats.Png
length = MaxBlockSize;
}
this.WriteChunk(stream, PngChunkTypes.Data, buffer, i * MaxBlockSize, length);
this.WriteChunk(stream, PngChunkType.Data, buffer, i * MaxBlockSize, length);
}
}
@ -599,7 +599,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
private void WriteEndChunk(Stream stream)
{
this.WriteChunk(stream, PngChunkTypes.End, null);
this.WriteChunk(stream, PngChunkType.End, null);
}
/// <summary>
@ -608,7 +608,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="type">The type of chunk to write.</param>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
private void WriteChunk(Stream stream, string type, byte[] data)
private void WriteChunk(Stream stream, PngChunkType type, byte[] data)
{
this.WriteChunk(stream, type, data, 0, data?.Length ?? 0);
}
@ -621,22 +621,16 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
/// <param name="offset">The position to offset the data at.</param>
/// <param name="length">The of the data to write.</param>
private void WriteChunk(Stream stream, string type, byte[] data, int offset, int length)
private void WriteChunk(Stream stream, PngChunkType type, byte[] data, int offset, int length)
{
BinaryPrimitives.WriteInt32BigEndian(this.intBuffer, length);
BinaryPrimitives.WriteInt32BigEndian(this.buffer, length);
BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type);
stream.Write(this.intBuffer, 0, 4); // write the length
this.chunkTypeBuffer[0] = (byte)type[0];
this.chunkTypeBuffer[1] = (byte)type[1];
this.chunkTypeBuffer[2] = (byte)type[2];
this.chunkTypeBuffer[3] = (byte)type[3];
stream.Write(this.chunkTypeBuffer, 0, 4);
stream.Write(this.buffer, 0, 8);
this.crc.Reset();
this.crc.Update(this.chunkTypeBuffer);
this.crc.Update(this.buffer.AsSpan(4, 4)); // Write the type buffer
if (data != null && length > 0)
{

12
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -242,10 +242,10 @@ namespace SixLabors.ImageSharp.Tests
}
[Theory]
[InlineData(PngChunkTypes.Header)]
[InlineData(PngChunkTypes.Palette)]
[InlineData(PngChunkTypeNames.Header)]
[InlineData(PngChunkTypeNames.Palette)]
// [InlineData(PngChunkTypes.Data)] //TODO: Figure out how to test this
[InlineData(PngChunkTypes.End)]
[InlineData(PngChunkTypeNames.End)]
public void Decode_IncorrectCRCForCriticalChunk_ExceptionIsThrown(string chunkName)
{
using (var memStream = new MemoryStream())
@ -266,9 +266,9 @@ namespace SixLabors.ImageSharp.Tests
}
[Theory]
[InlineData(PngChunkTypes.Gamma)]
[InlineData(PngChunkTypes.PaletteAlpha)]
[InlineData(PngChunkTypes.Physical)] // It's ok to test physical as we don't throw for duplicate chunks.
[InlineData(PngChunkTypeNames.Gamma)]
[InlineData(PngChunkTypeNames.PaletteAlpha)]
[InlineData(PngChunkTypeNames.Physical)] // It's ok to test physical as we don't throw for duplicate chunks.
//[InlineData(PngChunkTypes.Text)] //TODO: Figure out how to test this
public void Decode_IncorrectCRCForNonCriticalChunk_ExceptionIsThrown(string chunkName)
{

Loading…
Cancel
Save