Browse Source

Reduced intermediate allocations: Tiff

pull/2415/head
Günther Foidl 3 years ago
parent
commit
b099bda98b
  1. 11
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs
  2. 23
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs
  3. 30
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs
  4. 9
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs
  5. 38
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  6. 44
      src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs
  7. 4
      tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs
  8. 4
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs
  9. 15
      tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs

11
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs

@ -24,9 +24,9 @@ internal class BlackIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
TPixel color = default;
color.FromScaledVector4(Vector4.Zero);
byte[] buffer = new byte[4];
Span<byte> buffer = stackalloc byte[4];
int offset = 0;
for (int y = top; y < top + height; y++)
@ -37,8 +37,8 @@ internal class BlackIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float intensity = BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float intensity = BitConverter.ToSingle(buffer);
offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
@ -50,8 +50,7 @@ internal class BlackIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
float intensity = BitConverter.ToSingle(buffer, 0);
float intensity = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);

23
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs

@ -27,7 +27,7 @@ internal class RgbFloat323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
byte[] buffer = new byte[4];
Span<byte> buffer = stackalloc byte[4];
for (int y = top; y < top + height; y++)
{
@ -38,18 +38,18 @@ internal class RgbFloat323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float r = BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float r = BitConverter.ToSingle(buffer);
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float g = BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float g = BitConverter.ToSingle(buffer);
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float b = BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float b = BitConverter.ToSingle(buffer);
offset += 4;
var colorVector = new Vector4(r, g, b, 1.0f);
@ -61,16 +61,13 @@ internal class RgbFloat323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
float r = BitConverter.ToSingle(buffer, 0);
float r = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
float g = BitConverter.ToSingle(buffer, 0);
float g = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
float b = BitConverter.ToSingle(buffer, 0);
float b = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
var colorVector = new Vector4(r, g, b, 1.0f);

30
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs

@ -27,7 +27,7 @@ internal class RgbaFloat32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
byte[] buffer = new byte[4];
Span<byte> buffer = stackalloc byte[4];
for (int y = top; y < top + height; y++)
{
@ -38,23 +38,23 @@ internal class RgbaFloat32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float r = BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float r = BitConverter.ToSingle(buffer);
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float g = BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float g = BitConverter.ToSingle(buffer);
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float b = BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float b = BitConverter.ToSingle(buffer);
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float a = BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float a = BitConverter.ToSingle(buffer);
offset += 4;
var colorVector = new Vector4(r, g, b, a);
@ -66,20 +66,16 @@ internal class RgbaFloat32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
float r = BitConverter.ToSingle(buffer, 0);
float r = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
float g = BitConverter.ToSingle(buffer, 0);
float g = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
float b = BitConverter.ToSingle(buffer, 0);
float b = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
data.Slice(offset, 4).CopyTo(buffer);
float a = BitConverter.ToSingle(buffer, 0);
float a = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
var colorVector = new Vector4(r, g, b, a);

9
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs

@ -26,7 +26,7 @@ internal class WhiteIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
byte[] buffer = new byte[4];
Span<byte> buffer = stackalloc byte[4];
int offset = 0;
for (int y = top; y < top + height; y++)
@ -37,8 +37,8 @@ internal class WhiteIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
Array.Reverse(buffer);
float intensity = 1.0f - BitConverter.ToSingle(buffer, 0);
buffer.Reverse();
float intensity = 1.0f - BitConverter.ToSingle(buffer);
offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
@ -50,8 +50,7 @@ internal class WhiteIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 4).CopyTo(buffer);
float intensity = 1.0f - BitConverter.ToSingle(buffer, 0);
float intensity = 1.0f - BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);

38
src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

@ -29,11 +29,6 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// A scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] buffer = new byte[4];
/// <summary>
/// The global configuration.
/// </summary>
@ -157,7 +152,9 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
this.SanitizeAndSetEncoderOptions(bitsPerPixel, image.PixelType.BitsPerPixel, photometricInterpretation, compression, predictor);
using TiffStreamWriter writer = new(stream);
long ifdMarker = WriteHeader(writer);
Span<byte> buffer = stackalloc byte[4];
long ifdMarker = WriteHeader(writer, buffer);
Image<TPixel> metadataImage = image;
foreach (ImageFrame<TPixel> frame in image.Frames)
@ -171,7 +168,7 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
long currentOffset = writer.BaseStream.Position;
foreach ((long, uint) marker in this.frameMarkers)
{
writer.WriteMarkerFast(marker.Item1, marker.Item2);
writer.WriteMarkerFast(marker.Item1, marker.Item2, buffer);
}
writer.BaseStream.Seek(currentOffset, SeekOrigin.Begin);
@ -181,14 +178,15 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
/// Writes the TIFF file header.
/// </summary>
/// <param name="writer">The <see cref="TiffStreamWriter" /> to write data to.</param>
/// <param name="buffer">Scratch buffer with minimum size of 2.</param>
/// <returns>
/// The marker to write the first IFD offset.
/// </returns>
public static long WriteHeader(TiffStreamWriter writer)
public static long WriteHeader(TiffStreamWriter writer, Span<byte> buffer)
{
writer.Write(ByteOrderMarker);
writer.Write(TiffConstants.HeaderMagicNumber);
return writer.PlaceMarker();
writer.Write(ByteOrderMarker, buffer);
writer.Write(TiffConstants.HeaderMagicNumber, buffer);
return writer.PlaceMarker(buffer);
}
/// <summary>
@ -307,20 +305,22 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
entries.Sort((a, b) => (ushort)a.Tag - (ushort)b.Tag);
writer.Write((ushort)entries.Count);
Span<byte> buffer = stackalloc byte[4];
writer.Write((ushort)entries.Count, buffer);
foreach (IExifValue entry in entries)
{
writer.Write((ushort)entry.Tag);
writer.Write((ushort)entry.DataType);
writer.Write(ExifWriter.GetNumberOfComponents(entry));
writer.Write((ushort)entry.Tag, buffer);
writer.Write((ushort)entry.DataType, buffer);
writer.Write(ExifWriter.GetNumberOfComponents(entry), buffer);
uint length = ExifWriter.GetLength(entry);
if (length <= 4)
{
int sz = ExifWriter.WriteValue(entry, this.buffer, 0);
int sz = ExifWriter.WriteValue(entry, buffer, 0);
DebugGuard.IsTrue(sz == length, "Incorrect number of bytes written");
writer.WritePadded(this.buffer.AsSpan(0, sz));
writer.WritePadded(buffer.Slice(0, sz));
}
else
{
@ -328,12 +328,12 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
int sz = ExifWriter.WriteValue(entry, raw, 0);
DebugGuard.IsTrue(sz == raw.Length, "Incorrect number of bytes written");
largeDataBlocks.Add(raw);
writer.Write(dataOffset);
writer.Write(dataOffset, buffer);
dataOffset += (uint)(raw.Length + (raw.Length % 2));
}
}
long nextIfdMarker = writer.PlaceMarker();
long nextIfdMarker = writer.PlaceMarker(buffer);
foreach (byte[] dataBlock in largeDataBlocks)
{

44
src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs

@ -10,13 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers;
/// </summary>
internal sealed class TiffStreamWriter : IDisposable
{
private static readonly byte[] PaddingBytes = new byte[4];
/// <summary>
/// A scratch buffer to reduce allocations.
/// </summary>
private readonly byte[] buffer = new byte[4];
/// <summary>
/// Initializes a new instance of the <see cref="TiffStreamWriter"/> class.
/// </summary>
@ -41,11 +34,12 @@ internal sealed class TiffStreamWriter : IDisposable
/// <summary>
/// Writes an empty four bytes to the stream, returning the offset to be written later.
/// </summary>
/// <param name="buffer">Scratch buffer with minimum size of 4.</param>
/// <returns>The offset to be written later.</returns>
public long PlaceMarker()
public long PlaceMarker(Span<byte> buffer)
{
long offset = this.BaseStream.Position;
this.Write(0u);
this.Write(0u, buffer);
return offset;
}
@ -71,36 +65,38 @@ internal sealed class TiffStreamWriter : IDisposable
/// Writes a two-byte unsigned integer to the current stream.
/// </summary>
/// <param name="value">The two-byte unsigned integer to write.</param>
public void Write(ushort value)
/// <param name="buffer">Scratch buffer with minimum size of 2.</param>
public void Write(ushort value, Span<byte> buffer)
{
if (IsLittleEndian)
{
BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, value);
BinaryPrimitives.WriteUInt16LittleEndian(buffer, value);
}
else
{
BinaryPrimitives.WriteUInt16BigEndian(this.buffer, value);
BinaryPrimitives.WriteUInt16BigEndian(buffer, value);
}
this.BaseStream.Write(this.buffer.AsSpan(0, 2));
this.BaseStream.Write(buffer.Slice(0, 2));
}
/// <summary>
/// Writes a four-byte unsigned integer to the current stream.
/// </summary>
/// <param name="value">The four-byte unsigned integer to write.</param>
public void Write(uint value)
/// <param name="buffer">Scratch buffer with minimum size of 4.</param>
public void Write(uint value, Span<byte> buffer)
{
if (IsLittleEndian)
{
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value);
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
}
else
{
BinaryPrimitives.WriteUInt32BigEndian(this.buffer, value);
BinaryPrimitives.WriteUInt32BigEndian(buffer, value);
}
this.BaseStream.Write(this.buffer.AsSpan(0, 4));
this.BaseStream.Write(buffer.Slice(0, 4));
}
/// <summary>
@ -113,7 +109,10 @@ internal sealed class TiffStreamWriter : IDisposable
if (value.Length % 4 != 0)
{
this.BaseStream.Write(PaddingBytes, 0, 4 - (value.Length % 4));
// No allocation occurs, refers directly to assembly's data segment.
ReadOnlySpan<byte> paddingBytes = new byte[4] { 0x00, 0x00, 0x00, 0x00 };
paddingBytes = paddingBytes[..(4 - (value.Length % 4))];
this.BaseStream.Write(paddingBytes);
}
}
@ -122,18 +121,19 @@ internal sealed class TiffStreamWriter : IDisposable
/// </summary>
/// <param name="offset">The offset returned when placing the marker</param>
/// <param name="value">The four-byte unsigned integer to write.</param>
public void WriteMarker(long offset, uint value)
/// <param name="buffer">Scratch buffer.</param>
public void WriteMarker(long offset, uint value, Span<byte> buffer)
{
long back = this.BaseStream.Position;
this.BaseStream.Seek(offset, SeekOrigin.Begin);
this.Write(value);
this.Write(value, buffer);
this.BaseStream.Seek(back, SeekOrigin.Begin);
}
public void WriteMarkerFast(long offset, uint value)
public void WriteMarkerFast(long offset, uint value, Span<byte> buffer)
{
this.BaseStream.Seek(offset, SeekOrigin.Begin);
this.Write(value);
this.Write(value, buffer);
}
/// <summary>

4
tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs

@ -211,8 +211,8 @@ public class BigTiffMetadataTests
foreach (IExifValue entry in values)
{
writer.Write((ushort)entry.Tag);
writer.Write((ushort)entry.DataType);
writer.Write((ushort)entry.Tag, buffer);
writer.Write((ushort)entry.DataType, buffer);
WriteLong8(writer, buffer, ExifWriter.GetNumberOfComponents(entry));
uint length = ExifWriter.GetLength(entry);

4
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs

@ -19,7 +19,7 @@ public class TiffEncoderHeaderTests
using (TiffStreamWriter writer = new(stream))
{
long firstIfdMarker = TiffEncoderCore.WriteHeader(writer);
long firstIfdMarker = TiffEncoderCore.WriteHeader(writer, stackalloc byte[4]);
}
Assert.Equal(new byte[] { 0x49, 0x49, 42, 0, 0x00, 0x00, 0x00, 0x00 }, stream.ToArray());
@ -32,7 +32,7 @@ public class TiffEncoderHeaderTests
TiffEncoderCore encoder = new(Encoder, Configuration.Default.MemoryAllocator);
using TiffStreamWriter writer = new(stream);
long firstIfdMarker = TiffEncoderCore.WriteHeader(writer);
long firstIfdMarker = TiffEncoderCore.WriteHeader(writer, stackalloc byte[4]);
Assert.Equal(4, firstIfdMarker);
}
}

15
tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs

@ -53,7 +53,7 @@ public class TiffWriterTests
{
using var stream = new MemoryStream();
using var writer = new TiffStreamWriter(stream);
writer.Write(1234);
writer.Write(1234, stackalloc byte[2]);
Assert.Equal(new byte[] { 0xD2, 0x04 }, stream.ToArray());
}
@ -63,7 +63,7 @@ public class TiffWriterTests
{
using var stream = new MemoryStream();
using var writer = new TiffStreamWriter(stream);
writer.Write(12345678U);
writer.Write(12345678U, stackalloc byte[4]);
Assert.Equal(new byte[] { 0x4E, 0x61, 0xBC, 0x00 }, stream.ToArray());
}
@ -89,16 +89,17 @@ public class TiffWriterTests
public void WriteMarker_WritesToPlacedPosition()
{
using var stream = new MemoryStream();
Span<byte> buffer = stackalloc byte[4];
using (var writer = new TiffStreamWriter(stream))
{
writer.Write(0x11111111);
long marker = writer.PlaceMarker();
writer.Write(0x33333333);
writer.Write(0x11111111, buffer);
long marker = writer.PlaceMarker(buffer);
writer.Write(0x33333333, buffer);
writer.WriteMarker(marker, 0x12345678);
writer.WriteMarker(marker, 0x12345678, buffer);
writer.Write(0x44444444);
writer.Write(0x44444444, buffer);
}
Assert.Equal(

Loading…
Cancel
Save