Browse Source

Merge pull request #528 from carbon/memorypreview2

Simplify Png Decoding / Encoding
af/merge-core
James Jackson-South 8 years ago
committed by GitHub
parent
commit
d03d1d3045
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/ImageSharp/Formats/Png/IPngDecoderOptions.cs
  2. 2
      src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
  3. 2
      src/ImageSharp/Formats/Png/ImageExtensions.cs
  4. 33
      src/ImageSharp/Formats/Png/PngChunk.cs
  5. 22
      src/ImageSharp/Formats/Png/PngChunkType.cs
  6. 16
      src/ImageSharp/Formats/Png/PngConstants.cs
  7. 2
      src/ImageSharp/Formats/Png/PngDecoder.cs
  8. 136
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  9. 72
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  10. 12
      src/ImageSharp/Formats/Png/PngImageFormatDetector.cs
  11. 5
      src/ImageSharp/Formats/Png/Zlib/Adler32.cs
  12. 5
      src/ImageSharp/Formats/Png/Zlib/IChecksum.cs
  13. 21
      src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs
  14. 2
      src/ImageSharp/Image.LoadPixelData.cs
  15. 2
      src/ImageSharp/ImageExtensions.cs
  16. 2
      src/ImageSharp/Memory/BasicArrayBuffer.cs
  17. 12
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs
  18. 42
      tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs
  19. 29
      tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs
  20. 32
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

6
src/ImageSharp/Formats/Png/IPngDecoderOptions.cs

@ -1,11 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png
{
@ -24,4 +20,4 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
Encoding TextEncoding { get; }
}
}
}

2
src/ImageSharp/Formats/Png/IPngEncoderOptions.cs

@ -45,4 +45,4 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
bool WriteGamma { get; }
}
}
}

2
src/ImageSharp/Formats/Png/ImageExtensions.cs

@ -37,4 +37,4 @@ namespace SixLabors.ImageSharp
where TPixel : struct, IPixel<TPixel>
=> source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Png));
}
}
}

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

@ -8,11 +8,14 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Stores header information about a chunk.
/// </summary>
internal sealed class PngChunk
internal readonly struct PngChunk
{
public PngChunk(int length)
public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null, uint crc = 0)
{
this.Length = length;
this.Type = type;
this.Data = data;
this.Crc = crc;
}
/// <summary>
@ -24,21 +27,31 @@ namespace SixLabors.ImageSharp.Formats.Png
public int Length { get; }
/// <summary>
/// Gets or sets the chunk type as string with 4 chars.
/// Gets the chunk type.
/// The value is the equal to the UInt32BigEndian encoding of its 4 ASCII characters.
/// </summary>
public string Type { get; set; }
public PngChunkType Type { get; }
/// <summary>
/// Gets or sets the data bytes appropriate to the chunk type, if any.
/// This field can be of zero length.
/// Gets the data bytes appropriate to the chunk type, if any.
/// This field can be of zero length or null.
/// </summary>
public IManagedByteBuffer Data { get; set; }
public IManagedByteBuffer Data { get; }
/// <summary>
/// Gets or sets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk,
/// Gets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk,
/// including the chunk type code and chunk data fields, but not including the length field.
/// The CRC is always present, even for chunks containing no data
/// </summary>
public uint Crc { get; set; }
public uint Crc { get; }
/// <summary>
/// Gets a value indicating whether the given chunk is critical to decoding
/// </summary>
public bool IsCritical =>
this.Type == PngChunkType.Header ||
this.Type == PngChunkType.Palette ||
this.Type == PngChunkType.Data ||
this.Type == PngChunkType.End;
}
}
}

22
src/ImageSharp/Formats/Png/PngChunkTypes.cs → src/ImageSharp/Formats/Png/PngChunkType.cs

@ -4,57 +4,57 @@
namespace SixLabors.ImageSharp.Formats.Png
{
/// <summary>
/// Contains a list of possible chunk type identifiers.
/// Contains a list of of chunk types.
/// </summary>
internal static class PngChunkTypes
internal enum PngChunkType : uint
{
/// <summary>
/// The first chunk in a png file. Can only exists once. Contains
/// common information like the width and the height of the image or
/// the used compression method.
/// </summary>
public const string Header = "IHDR";
Header = 0x49484452U, // IHDR
/// <summary>
/// The PLTE chunk contains from 1 to 256 palette entries, each a three byte
/// series in the RGB format.
/// </summary>
public const string Palette = "PLTE";
Palette = 0x504C5445U, // PLTE
/// <summary>
/// The IDAT chunk contains the actual image data. The image can contains more
/// than one chunk of this type. All chunks together are the whole image.
/// </summary>
public const string Data = "IDAT";
Data = 0x49444154U, // IDAT
/// <summary>
/// This chunk must appear last. It marks the end of the PNG data stream.
/// The chunk's data field is empty.
/// </summary>
public const string End = "IEND";
End = 0x49454E44U, // IEND
/// <summary>
/// This chunk specifies that the image uses simple transparency:
/// either alpha values associated with palette entries (for indexed-color images)
/// or a single transparent color (for grayscale and true color images).
/// </summary>
public const string PaletteAlpha = "tRNS";
PaletteAlpha = 0x74524E53U, // tRNS
/// <summary>
/// Textual information that the encoder wishes to record with the image can be stored in
/// tEXt chunks. Each tEXt chunk contains a keyword and a text string.
/// </summary>
public const string Text = "tEXt";
Text = 0x74455874U, // tEXt
/// <summary>
/// This chunk specifies the relationship between the image samples and the desired
/// display output intensity.
/// </summary>
public const string Gamma = "gAMA";
Gamma = 0x67414D41U, // gAMA
/// <summary>
/// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image.
/// </summary>
public const string Physical = "pHYs";
Physical = 0x70485973U // pHYs
}
}
}

16
src/ImageSharp/Formats/Png/PngConstants.cs

@ -25,5 +25,21 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The list of file extensions that equate to a png.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "png" };
public static readonly byte[] HeaderBytes = {
0x89, // Set the high bit.
0x50, // P
0x4E, // N
0x47, // G
0x0D, // Line ending CRLF
0x0A, // Line ending CRLF
0x1A, // EOF
0x0A // LF
};
/// <summary>
/// The header bytes as a big endian coded ulong.
/// </summary>
public const ulong HeaderValue = 0x89504E470D0A1A0AUL;
}
}

2
src/ImageSharp/Formats/Png/PngDecoder.cs

@ -60,4 +60,4 @@ namespace SixLabors.ImageSharp.Formats.Png
return decoder.Identify(stream);
}
}
}
}

136
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,33 +236,28 @@ 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;
}
}
finally
{
// Data is rented in ReadChunkData()
if (chunk.Data != null)
{
chunk.Data.Dispose();
chunk.Data = null;
}
chunk.Data?.Dispose(); // Data is rented in ReadChunkData()
}
}
}
@ -303,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;
}
@ -383,20 +373,6 @@ namespace SixLabors.ImageSharp.Formats.Png
return result;
}
/// <summary>
/// Returns a value indicating whether the given chunk is critical to decoding
/// </summary>
/// <param name="chunk">The chunk</param>
/// <returns>The <see cref="bool"/></returns>
private static bool IsCriticalChunk(PngChunk chunk)
{
return
chunk.Type == PngChunkTypes.Header ||
chunk.Type == PngChunkTypes.Palette ||
chunk.Type == PngChunkTypes.Data ||
chunk.Type == PngChunkTypes.End;
}
/// <summary>
/// Reads an integer value from 2 consecutive bytes in LSB order
/// </summary>
@ -1217,38 +1193,67 @@ namespace SixLabors.ImageSharp.Formats.Png
return false;
}
chunk = new PngChunk(length);
if (chunk.Length < 0 || chunk.Length > this.currentStream.Length - this.currentStream.Position)
while (length < 0 || length > (this.currentStream.Length - this.currentStream.Position))
{
// Not a valid chunk so we skip back all but one of the four bytes we have just read.
// That lets us read one byte at a time until we reach a known chunk.
this.currentStream.Position -= 3;
return true;
length = this.ReadChunkLength();
if (length == -1)
{
chunk = default;
return false;
}
}
this.ReadChunkType(chunk);
PngChunkType type = this.ReadChunkType();
if (chunk.Type == PngChunkTypes.Data)
// NOTE: Reading the chunk data is the responsible of the caller
if (type == PngChunkType.Data)
{
chunk = new PngChunk(length, type);
return true;
}
this.ReadChunkData(chunk);
this.ReadChunkCrc(chunk);
chunk = new PngChunk(
length: length,
type: type,
data: this.ReadChunkData(length),
crc: this.ReadChunkCrc());
if (chunk.IsCritical)
{
this.ValidateChunk(chunk);
}
return true;
}
private void ValidateChunk(in PngChunk chunk)
{
this.crc.Reset();
this.crc.Update(this.chunkTypeBuffer);
this.crc.Update(chunk.Data.Span);
if (this.crc.Value != chunk.Crc)
{
string chunkTypeName = Encoding.UTF8.GetString(this.chunkTypeBuffer, 0, 4);
throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
}
}
/// <summary>
/// Reads the cycle redundancy chunk from the data.
/// </summary>
/// <param name="chunk">The chunk.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid or corrupt.
/// </exception>
private void ReadChunkCrc(PngChunk chunk)
private uint ReadChunkCrc()
{
int numBytes = this.currentStream.Read(this.crcBuffer, 0, 4);
@ -1257,22 +1262,13 @@ namespace SixLabors.ImageSharp.Formats.Png
throw new ImageFormatException("Image stream is not valid!");
}
chunk.Crc = BinaryPrimitives.ReadUInt32BigEndian(this.crcBuffer);
this.crc.Reset();
this.crc.Update(this.chunkTypeBuffer);
this.crc.Update(new ReadOnlySpan<byte>(chunk.Data.Array, 0, chunk.Length));
if (this.crc.Value != chunk.Crc && IsCriticalChunk(chunk))
{
throw new ImageFormatException($"CRC Error. PNG {chunk.Type} chunk is corrupt!");
}
return BinaryPrimitives.ReadUInt32BigEndian(this.crcBuffer);
}
/// <summary>
/// Skips the chunk data and the cycle redundancy chunk read from the data.
/// </summary>
private void SkipChunkDataAndCrc(PngChunk chunk)
private void SkipChunkDataAndCrc(in PngChunk chunk)
{
this.currentStream.Skip(chunk.Length);
this.currentStream.Skip(4);
@ -1281,35 +1277,33 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Reads the chunk data from the stream.
/// </summary>
/// <param name="chunk">The chunk.</param>
private void ReadChunkData(PngChunk chunk)
/// <param name="length">The length of the chunk data to read.</param>
private IManagedByteBuffer ReadChunkData(int length)
{
// We rent the buffer here to return it afterwards in Decode()
chunk.Data = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(chunk.Length);
this.currentStream.Read(chunk.Data.Array, 0, chunk.Length);
IManagedByteBuffer buffer = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(length);
this.currentStream.Read(buffer.Array, 0, length);
return buffer;
}
/// <summary>
/// Identifies the chunk type from the chunk.
/// </summary>
/// <param name="chunk">The chunk.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
private void ReadChunkType(PngChunk chunk)
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];
chunk.Type = new string(this.chars);
return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.chunkTypeBuffer.AsSpan());
}
/// <summary>
@ -1359,4 +1353,4 @@ namespace SixLabors.ImageSharp.Formats.Png
this.scanline = temp;
}
}
}
}

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

@ -5,7 +5,6 @@ using System;
using System.Buffers.Binary;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
@ -28,20 +27,15 @@ 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.
/// </summary>
private readonly byte[] chunkDataBuffer = new byte[16];
/// <summary>
/// Reusable buffer for writing int data.
/// </summary>
private readonly byte[] intBuffer = new byte[4];
/// <summary>
/// Reusable crc for validating chunks.
/// </summary>
@ -173,17 +167,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.width = image.Width;
this.height = image.Height;
// Write the png header.
this.chunkDataBuffer[0] = 0x89; // Set the high bit.
this.chunkDataBuffer[1] = 0x50; // P
this.chunkDataBuffer[2] = 0x4E; // N
this.chunkDataBuffer[3] = 0x47; // G
this.chunkDataBuffer[4] = 0x0D; // Line ending CRLF
this.chunkDataBuffer[5] = 0x0A; // Line ending CRLF
this.chunkDataBuffer[6] = 0x1A; // EOF
this.chunkDataBuffer[7] = 0x0A; // LF
stream.Write(this.chunkDataBuffer, 0, 8);
stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);
QuantizedFrame<TPixel> quantized = null;
if (this.pngColorType == PngColorType.Palette)
@ -415,8 +399,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="header">The <see cref="PngHeader"/>.</param>
private void WriteHeaderChunk(Stream stream, in PngHeader header)
{
BinaryPrimitives.WriteInt32BigEndian(new Span<byte>(this.chunkDataBuffer, 0, 4), header.Width);
BinaryPrimitives.WriteInt32BigEndian(new Span<byte>(this.chunkDataBuffer, 4, 4), header.Height);
BinaryPrimitives.WriteInt32BigEndian(this.chunkDataBuffer.AsSpan(0, 4), header.Width);
BinaryPrimitives.WriteInt32BigEndian(this.chunkDataBuffer.AsSpan(4, 4), header.Height);
this.chunkDataBuffer[8] = header.BitDepth;
this.chunkDataBuffer[9] = (byte)header.ColorType;
@ -424,7 +408,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>
@ -443,7 +427,7 @@ namespace SixLabors.ImageSharp.Formats.Png
// Get max colors for bit depth.
int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;
var rgba = default(Rgba32);
Rgba32 rgba = default;
bool anyAlpha = false;
using (IManagedByteBuffer colorTable = this.memoryManager.AllocateManagedByteBuffer(colorTableLength))
@ -475,12 +459,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);
}
}
}
@ -500,12 +484,12 @@ namespace SixLabors.ImageSharp.Formats.Png
int dpmX = (int)Math.Round(image.MetaData.HorizontalResolution * 39.3700787D);
int dpmY = (int)Math.Round(image.MetaData.VerticalResolution * 39.3700787D);
BinaryPrimitives.WriteInt32BigEndian(new Span<byte>(this.chunkDataBuffer, 0, 4), dpmX);
BinaryPrimitives.WriteInt32BigEndian(new Span<byte>(this.chunkDataBuffer, 4, 4), dpmY);
BinaryPrimitives.WriteInt32BigEndian(this.chunkDataBuffer.AsSpan(0, 4), dpmX);
BinaryPrimitives.WriteInt32BigEndian(this.chunkDataBuffer.AsSpan(4, 4), dpmY);
this.chunkDataBuffer[8] = 1;
this.WriteChunk(stream, PngChunkTypes.Physical, this.chunkDataBuffer, 0, 9);
this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9);
}
}
@ -520,9 +504,9 @@ namespace SixLabors.ImageSharp.Formats.Png
// 4-byte unsigned integer of gamma * 100,000.
uint gammaValue = (uint)(this.gamma * 100_000F);
BinaryPrimitives.WriteUInt32BigEndian(new Span<byte>(this.chunkDataBuffer, 0, 4), gammaValue);
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);
}
}
@ -590,7 +574,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);
}
}
@ -600,7 +584,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>
@ -609,7 +593,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);
}
@ -622,33 +606,27 @@ 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);
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];
BinaryPrimitives.WriteInt32BigEndian(this.buffer, length);
BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type);
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)
{
stream.Write(data, offset, length);
this.crc.Update(new ReadOnlySpan<byte>(data, offset, length));
this.crc.Update(data.AsSpan(offset, length));
}
BinaryPrimitives.WriteUInt32BigEndian(this.intBuffer, (uint)this.crc.Value);
BinaryPrimitives.WriteUInt32BigEndian(this.buffer, (uint)this.crc.Value);
stream.Write(this.intBuffer, 0, 4); // write the crc
stream.Write(this.buffer, 0, 4); // write the crc
}
}
}

12
src/ImageSharp/Formats/Png/PngImageFormatDetector.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
namespace SixLabors.ImageSharp.Formats.Png
{
@ -26,16 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{
// TODO: This should be in constants
return header.Length >= this.HeaderSize &&
header[0] == 0x89 &&
header[1] == 0x50 && // P
header[2] == 0x4E && // N
header[3] == 0x47 && // G
header[4] == 0x0D && // CR
header[5] == 0x0A && // LF
header[6] == 0x1A && // EOF
header[7] == 0x0A; // LF
return header.Length >= this.HeaderSize && BinaryPrimitives.ReadUInt64BigEndian(header) == PngConstants.HeaderValue;
}
}
}

5
src/ImageSharp/Formats/Png/Zlib/Adler32.cs

@ -78,10 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
public long Value
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.checksum;
}
get => this.checksum;
}
/// <inheritdoc/>

5
src/ImageSharp/Formats/Png/Zlib/IChecksum.cs

@ -17,10 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <summary>
/// Gets the data checksum computed so far.
/// </summary>
long Value
{
get;
}
long Value { get; }
/// <summary>
/// Resets the data checksum as if no update was ever called.

21
src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs

@ -113,26 +113,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
public override bool CanWrite => true;
/// <inheritdoc/>
public override long Length
{
get
{
throw new NotSupportedException();
}
}
public override long Length => throw new NotSupportedException();
/// <inheritdoc/>
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
/// <inheritdoc/>
@ -163,7 +150,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
public override void Write(byte[] buffer, int offset, int count)
{
this.deflateStream.Write(buffer, offset, count);
this.adler32.Update(new ReadOnlySpan<byte>(buffer, offset, count));
this.adler32.Update(buffer.AsSpan(offset, count));
}
/// <inheritdoc/>

2
src/ImageSharp/Image.LoadPixelData.cs

@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, TPixel[] data, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
return LoadPixelData(config, new Span<TPixel>(data), width, height);
return LoadPixelData(config, data.AsSpan(), width, height);
}
/// <summary>

2
src/ImageSharp/ImageExtensions.cs

@ -143,7 +143,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, TPixel[] buffer)
where TPixel : struct, IPixel<TPixel>
=> SavePixelData(source, new Span<TPixel>(buffer));
=> SavePixelData(source, buffer.AsSpan());
/// <summary>
/// Saves the raw image pixels to a byte array in row-major order.

2
src/ImageSharp/Memory/BasicArrayBuffer.cs

@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Memory
public int Length { get; }
public Span<T> Span => new Span<T>(this.Array, 0, this.Length);
public Span<T> Span => this.Array.AsSpan(0, this.Length);
/// <summary>
/// Returns a reference to specified element of the buffer.

12
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
/// <returns>the value</returns>
public ushort ReadUInt16()
{
return BinaryPrimitives.ReadUInt16BigEndian(new Span<byte>(this.data, this.AddIndex(2), 2));
return BinaryPrimitives.ReadUInt16BigEndian(this.data.AsSpan(this.AddIndex(2), 2));
}
/// <summary>
@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
/// <returns>the value</returns>
public short ReadInt16()
{
return BinaryPrimitives.ReadInt16BigEndian(new Span<byte>(this.data, this.AddIndex(2), 2));
return BinaryPrimitives.ReadInt16BigEndian(this.data.AsSpan(this.AddIndex(2), 2));
}
/// <summary>
@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
/// <returns>the value</returns>
public uint ReadUInt32()
{
return BinaryPrimitives.ReadUInt32BigEndian(new Span<byte>(this.data, this.AddIndex(4), 4));
return BinaryPrimitives.ReadUInt32BigEndian(this.data.AsSpan(this.AddIndex(4), 4));
}
/// <summary>
@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
/// <returns>the value</returns>
public int ReadInt32()
{
return BinaryPrimitives.ReadInt32BigEndian(new Span<byte>(this.data, this.AddIndex(4), 4));
return BinaryPrimitives.ReadInt32BigEndian(this.data.AsSpan(this.AddIndex(4), 4));
}
/// <summary>
@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
/// <returns>the value</returns>
public ulong ReadUInt64()
{
return BinaryPrimitives.ReadUInt64BigEndian(new Span<byte>(this.data, this.AddIndex(8), 8));
return BinaryPrimitives.ReadUInt64BigEndian(this.data.AsSpan(this.AddIndex(8), 8));
}
/// <summary>
@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
/// <returns>the value</returns>
public long ReadInt64()
{
return BinaryPrimitives.ReadInt64BigEndian(new Span<byte>(this.data, this.AddIndex(8), 8));
return BinaryPrimitives.ReadInt64BigEndian(this.data.AsSpan(this.AddIndex(8), 8));
}
/// <summary>

42
tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs

@ -1,15 +1,14 @@
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
using System;
using System;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using Xunit;
using Xunit.Abstractions;
using Xunit;
using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
public static class DCTTests
{
public class FastFloatingPoint : JpegFixture
@ -19,7 +18,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
}
[Fact]
public void iDCT2D8x4_LeftPart()
{
@ -28,10 +26,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D8x4_32f(sourceArray, expectedDestArray);
Block8x8F source = new Block8x8F();
var source = new Block8x8F();
source.LoadFrom(sourceArray);
Block8x8F dest = new Block8x8F();
var dest = new Block8x8F();
FastFloatingPointDCT.IDCT8x4_LeftPart(ref source, ref dest);
@ -51,12 +49,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
float[] sourceArray = JpegFixture.Create8x8FloatData();
float[] expectedDestArray = new float[64];
ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D8x4_32f(sourceArray.AsSpan().Slice(4), expectedDestArray.AsSpan().Slice(4));
ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D8x4_32f(sourceArray.AsSpan(4), expectedDestArray.AsSpan(4));
Block8x8F source = new Block8x8F();
var source = new Block8x8F();
source.LoadFrom(sourceArray);
Block8x8F dest = new Block8x8F();
var dest = new Block8x8F();
FastFloatingPointDCT.IDCT8x4_RightPart(ref source, ref dest);
@ -115,10 +113,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void FDCT8x4_LeftPart(int seed)
{
Span<float> src = JpegFixture.Create8x8RoundedRandomFloatData(-200, 200, seed);
Block8x8F srcBlock = new Block8x8F();
var srcBlock = new Block8x8F();
srcBlock.LoadFrom(src);
Block8x8F destBlock = new Block8x8F();
var destBlock = new Block8x8F();
float[] expectedDest = new float[64];
@ -137,14 +135,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void FDCT8x4_RightPart(int seed)
{
Span<float> src = JpegFixture.Create8x8RoundedRandomFloatData(-200, 200, seed);
Block8x8F srcBlock = new Block8x8F();
var srcBlock = new Block8x8F();
srcBlock.LoadFrom(src);
Block8x8F destBlock = new Block8x8F();
var destBlock = new Block8x8F();
float[] expectedDest = new float[64];
ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan().Slice(4));
ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4));
FastFloatingPointDCT.FDCT8x4_RightPart(ref srcBlock, ref destBlock);
float[] actualDest = new float[64];
@ -159,14 +157,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void TransformFDCT(int seed)
{
Span<float> src = JpegFixture.Create8x8RoundedRandomFloatData(-200, 200, seed);
Block8x8F srcBlock = new Block8x8F();
var srcBlock = new Block8x8F();
srcBlock.LoadFrom(src);
Block8x8F destBlock = new Block8x8F();
var destBlock = new Block8x8F();
float[] expectedDest = new float[64];
float[] temp1 = new float[64];
Block8x8F temp2 = new Block8x8F();
var temp2 = new Block8x8F();
ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true);
FastFloatingPointDCT.TransformFDCT(ref srcBlock, ref destBlock, ref temp2, false);

29
tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs

@ -0,0 +1,29 @@
using System;
using System.Buffers.Binary;
using System.Text;
using SixLabors.ImageSharp.Formats.Png;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Png
{
public class PngChunkTypeTests
{
[Fact]
public void ChunkTypeIdsAreCorrect()
{
Assert.Equal(PngChunkType.Header, GetType("IHDR"));
Assert.Equal(PngChunkType.Palette, GetType("PLTE"));
Assert.Equal(PngChunkType.Data, GetType("IDAT"));
Assert.Equal(PngChunkType.End, GetType("IEND"));
Assert.Equal(PngChunkType.PaletteAlpha, GetType("tRNS"));
Assert.Equal(PngChunkType.Text, GetType("tEXt"));
Assert.Equal(PngChunkType.Gamma, GetType("gAMA"));
Assert.Equal(PngChunkType.Physical, GetType("pHYs"));
}
private static PngChunkType GetType(string text)
{
return (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(Encoding.UTF8.GetBytes(text));
}
}
}

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

@ -2,15 +2,14 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.IO.Compression;
using System.Text;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests
{
using System.Buffers.Binary;
using System.Linq;
using SixLabors.ImageSharp.Formats.Png;
@ -242,12 +241,14 @@ namespace SixLabors.ImageSharp.Tests
}
[Theory]
[InlineData(PngChunkTypes.Header)]
[InlineData(PngChunkTypes.Palette)]
[InlineData((uint)PngChunkType.Header)] // IHDR
[InlineData((uint)PngChunkType.Palette)] // PLTE
// [InlineData(PngChunkTypes.Data)] //TODO: Figure out how to test this
[InlineData(PngChunkTypes.End)]
public void Decode_IncorrectCRCForCriticalChunk_ExceptionIsThrown(string chunkName)
[InlineData((uint)PngChunkType.End)] // IEND
public void Decode_IncorrectCRCForCriticalChunk_ExceptionIsThrown(uint chunkType)
{
string chunkName = GetChunkTypeName(chunkType);
using (var memStream = new MemoryStream())
{
WriteHeaderChunk(memStream);
@ -266,12 +267,14 @@ 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((uint)PngChunkType.Gamma)] // gAMA
[InlineData((uint)PngChunkType.PaletteAlpha)] // tRNS
[InlineData((uint)PngChunkType.Physical)] // pHYs: 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)
public void Decode_IncorrectCRCForNonCriticalChunk_ExceptionIsThrown(uint chunkType)
{
string chunkName = GetChunkTypeName(chunkType);
using (var memStream = new MemoryStream())
{
WriteHeaderChunk(memStream);
@ -283,6 +286,15 @@ namespace SixLabors.ImageSharp.Tests
}
}
private static string GetChunkTypeName(uint value)
{
byte[] data = new byte[4];
BinaryPrimitives.WriteUInt32BigEndian(data, value);
return Encoding.ASCII.GetString(data);
}
private static void WriteHeaderChunk(MemoryStream memStream)
{
// Writes a 1x1 32bit png header chunk containing a single black pixel

Loading…
Cancel
Save