Skip to content

Commit b099bda

Browse files
committed
Reduced intermediate allocations: Tiff
1 parent 9756ae9 commit b099bda

File tree

9 files changed

+85
-93
lines changed

9 files changed

+85
-93
lines changed

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ internal class BlackIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
2424
/// <inheritdoc/>
2525
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
2626
{
27-
var color = default(TPixel);
27+
TPixel color = default;
2828
color.FromScaledVector4(Vector4.Zero);
29-
byte[] buffer = new byte[4];
29+
Span<byte> buffer = stackalloc byte[4];
3030

3131
int offset = 0;
3232
for (int y = top; y < top + height; y++)
@@ -37,8 +37,8 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
3737
for (int x = 0; x < pixelRow.Length; x++)
3838
{
3939
data.Slice(offset, 4).CopyTo(buffer);
40-
Array.Reverse(buffer);
41-
float intensity = BitConverter.ToSingle(buffer, 0);
40+
buffer.Reverse();
41+
float intensity = BitConverter.ToSingle(buffer);
4242
offset += 4;
4343

4444
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
@@ -50,8 +50,7 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
5050
{
5151
for (int x = 0; x < pixelRow.Length; x++)
5252
{
53-
data.Slice(offset, 4).CopyTo(buffer);
54-
float intensity = BitConverter.ToSingle(buffer, 0);
53+
float intensity = BitConverter.ToSingle(data.Slice(offset, 4));
5554
offset += 4;
5655

5756
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);

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

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
2727
var color = default(TPixel);
2828
color.FromScaledVector4(Vector4.Zero);
2929
int offset = 0;
30-
byte[] buffer = new byte[4];
30+
Span<byte> buffer = stackalloc byte[4];
3131

3232
for (int y = top; y < top + height; y++)
3333
{
@@ -38,18 +38,18 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
3838
for (int x = 0; x < pixelRow.Length; x++)
3939
{
4040
data.Slice(offset, 4).CopyTo(buffer);
41-
Array.Reverse(buffer);
42-
float r = BitConverter.ToSingle(buffer, 0);
41+
buffer.Reverse();
42+
float r = BitConverter.ToSingle(buffer);
4343
offset += 4;
4444

4545
data.Slice(offset, 4).CopyTo(buffer);
46-
Array.Reverse(buffer);
47-
float g = BitConverter.ToSingle(buffer, 0);
46+
buffer.Reverse();
47+
float g = BitConverter.ToSingle(buffer);
4848
offset += 4;
4949

5050
data.Slice(offset, 4).CopyTo(buffer);
51-
Array.Reverse(buffer);
52-
float b = BitConverter.ToSingle(buffer, 0);
51+
buffer.Reverse();
52+
float b = BitConverter.ToSingle(buffer);
5353
offset += 4;
5454

5555
var colorVector = new Vector4(r, g, b, 1.0f);
@@ -61,16 +61,13 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
6161
{
6262
for (int x = 0; x < pixelRow.Length; x++)
6363
{
64-
data.Slice(offset, 4).CopyTo(buffer);
65-
float r = BitConverter.ToSingle(buffer, 0);
64+
float r = BitConverter.ToSingle(data.Slice(offset, 4));
6665
offset += 4;
6766

68-
data.Slice(offset, 4).CopyTo(buffer);
69-
float g = BitConverter.ToSingle(buffer, 0);
67+
float g = BitConverter.ToSingle(data.Slice(offset, 4));
7068
offset += 4;
7169

72-
data.Slice(offset, 4).CopyTo(buffer);
73-
float b = BitConverter.ToSingle(buffer, 0);
70+
float b = BitConverter.ToSingle(data.Slice(offset, 4));
7471
offset += 4;
7572

7673
var colorVector = new Vector4(r, g, b, 1.0f);

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

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
2727
var color = default(TPixel);
2828
color.FromScaledVector4(Vector4.Zero);
2929
int offset = 0;
30-
byte[] buffer = new byte[4];
30+
Span<byte> buffer = stackalloc byte[4];
3131

3232
for (int y = top; y < top + height; y++)
3333
{
@@ -38,23 +38,23 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
3838
for (int x = 0; x < pixelRow.Length; x++)
3939
{
4040
data.Slice(offset, 4).CopyTo(buffer);
41-
Array.Reverse(buffer);
42-
float r = BitConverter.ToSingle(buffer, 0);
41+
buffer.Reverse();
42+
float r = BitConverter.ToSingle(buffer);
4343
offset += 4;
4444

4545
data.Slice(offset, 4).CopyTo(buffer);
46-
Array.Reverse(buffer);
47-
float g = BitConverter.ToSingle(buffer, 0);
46+
buffer.Reverse();
47+
float g = BitConverter.ToSingle(buffer);
4848
offset += 4;
4949

5050
data.Slice(offset, 4).CopyTo(buffer);
51-
Array.Reverse(buffer);
52-
float b = BitConverter.ToSingle(buffer, 0);
51+
buffer.Reverse();
52+
float b = BitConverter.ToSingle(buffer);
5353
offset += 4;
5454

5555
data.Slice(offset, 4).CopyTo(buffer);
56-
Array.Reverse(buffer);
57-
float a = BitConverter.ToSingle(buffer, 0);
56+
buffer.Reverse();
57+
float a = BitConverter.ToSingle(buffer);
5858
offset += 4;
5959

6060
var colorVector = new Vector4(r, g, b, a);
@@ -66,20 +66,16 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
6666
{
6767
for (int x = 0; x < pixelRow.Length; x++)
6868
{
69-
data.Slice(offset, 4).CopyTo(buffer);
70-
float r = BitConverter.ToSingle(buffer, 0);
69+
float r = BitConverter.ToSingle(data.Slice(offset, 4));
7170
offset += 4;
7271

73-
data.Slice(offset, 4).CopyTo(buffer);
74-
float g = BitConverter.ToSingle(buffer, 0);
72+
float g = BitConverter.ToSingle(data.Slice(offset, 4));
7573
offset += 4;
7674

77-
data.Slice(offset, 4).CopyTo(buffer);
78-
float b = BitConverter.ToSingle(buffer, 0);
75+
float b = BitConverter.ToSingle(data.Slice(offset, 4));
7976
offset += 4;
8077

81-
data.Slice(offset, 4).CopyTo(buffer);
82-
float a = BitConverter.ToSingle(buffer, 0);
78+
float a = BitConverter.ToSingle(data.Slice(offset, 4));
8379
offset += 4;
8480

8581
var colorVector = new Vector4(r, g, b, a);

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
2626
{
2727
var color = default(TPixel);
2828
color.FromScaledVector4(Vector4.Zero);
29-
byte[] buffer = new byte[4];
29+
Span<byte> buffer = stackalloc byte[4];
3030

3131
int offset = 0;
3232
for (int y = top; y < top + height; y++)
@@ -37,8 +37,8 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
3737
for (int x = 0; x < pixelRow.Length; x++)
3838
{
3939
data.Slice(offset, 4).CopyTo(buffer);
40-
Array.Reverse(buffer);
41-
float intensity = 1.0f - BitConverter.ToSingle(buffer, 0);
40+
buffer.Reverse();
41+
float intensity = 1.0f - BitConverter.ToSingle(buffer);
4242
offset += 4;
4343

4444
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
@@ -50,8 +50,7 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
5050
{
5151
for (int x = 0; x < pixelRow.Length; x++)
5252
{
53-
data.Slice(offset, 4).CopyTo(buffer);
54-
float intensity = 1.0f - BitConverter.ToSingle(buffer, 0);
53+
float intensity = 1.0f - BitConverter.ToSingle(data.Slice(offset, 4));
5554
offset += 4;
5655

5756
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);

src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,6 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
2929
/// </summary>
3030
private readonly MemoryAllocator memoryAllocator;
3131

32-
/// <summary>
33-
/// A scratch buffer to reduce allocations.
34-
/// </summary>
35-
private readonly byte[] buffer = new byte[4];
36-
3732
/// <summary>
3833
/// The global configuration.
3934
/// </summary>
@@ -157,7 +152,9 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
157152
this.SanitizeAndSetEncoderOptions(bitsPerPixel, image.PixelType.BitsPerPixel, photometricInterpretation, compression, predictor);
158153

159154
using TiffStreamWriter writer = new(stream);
160-
long ifdMarker = WriteHeader(writer);
155+
Span<byte> buffer = stackalloc byte[4];
156+
157+
long ifdMarker = WriteHeader(writer, buffer);
161158

162159
Image<TPixel> metadataImage = image;
163160
foreach (ImageFrame<TPixel> frame in image.Frames)
@@ -171,7 +168,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
171168
long currentOffset = writer.BaseStream.Position;
172169
foreach ((long, uint) marker in this.frameMarkers)
173170
{
174-
writer.WriteMarkerFast(marker.Item1, marker.Item2);
171+
writer.WriteMarkerFast(marker.Item1, marker.Item2, buffer);
175172
}
176173

177174
writer.BaseStream.Seek(currentOffset, SeekOrigin.Begin);
@@ -181,14 +178,15 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
181178
/// Writes the TIFF file header.
182179
/// </summary>
183180
/// <param name="writer">The <see cref="TiffStreamWriter" /> to write data to.</param>
181+
/// <param name="buffer">Scratch buffer with minimum size of 2.</param>
184182
/// <returns>
185183
/// The marker to write the first IFD offset.
186184
/// </returns>
187-
public static long WriteHeader(TiffStreamWriter writer)
185+
public static long WriteHeader(TiffStreamWriter writer, Span<byte> buffer)
188186
{
189-
writer.Write(ByteOrderMarker);
190-
writer.Write(TiffConstants.HeaderMagicNumber);
191-
return writer.PlaceMarker();
187+
writer.Write(ByteOrderMarker, buffer);
188+
writer.Write(TiffConstants.HeaderMagicNumber, buffer);
189+
return writer.PlaceMarker(buffer);
192190
}
193191

194192
/// <summary>
@@ -307,33 +305,35 @@ private long WriteIfd(TiffStreamWriter writer, List<IExifValue> entries)
307305

308306
entries.Sort((a, b) => (ushort)a.Tag - (ushort)b.Tag);
309307

310-
writer.Write((ushort)entries.Count);
308+
Span<byte> buffer = stackalloc byte[4];
309+
310+
writer.Write((ushort)entries.Count, buffer);
311311

312312
foreach (IExifValue entry in entries)
313313
{
314-
writer.Write((ushort)entry.Tag);
315-
writer.Write((ushort)entry.DataType);
316-
writer.Write(ExifWriter.GetNumberOfComponents(entry));
314+
writer.Write((ushort)entry.Tag, buffer);
315+
writer.Write((ushort)entry.DataType, buffer);
316+
writer.Write(ExifWriter.GetNumberOfComponents(entry), buffer);
317317

318318
uint length = ExifWriter.GetLength(entry);
319319
if (length <= 4)
320320
{
321-
int sz = ExifWriter.WriteValue(entry, this.buffer, 0);
321+
int sz = ExifWriter.WriteValue(entry, buffer, 0);
322322
DebugGuard.IsTrue(sz == length, "Incorrect number of bytes written");
323-
writer.WritePadded(this.buffer.AsSpan(0, sz));
323+
writer.WritePadded(buffer.Slice(0, sz));
324324
}
325325
else
326326
{
327327
byte[] raw = new byte[length];
328328
int sz = ExifWriter.WriteValue(entry, raw, 0);
329329
DebugGuard.IsTrue(sz == raw.Length, "Incorrect number of bytes written");
330330
largeDataBlocks.Add(raw);
331-
writer.Write(dataOffset);
331+
writer.Write(dataOffset, buffer);
332332
dataOffset += (uint)(raw.Length + (raw.Length % 2));
333333
}
334334
}
335335

336-
long nextIfdMarker = writer.PlaceMarker();
336+
long nextIfdMarker = writer.PlaceMarker(buffer);
337337

338338
foreach (byte[] dataBlock in largeDataBlocks)
339339
{

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

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers;
1010
/// </summary>
1111
internal sealed class TiffStreamWriter : IDisposable
1212
{
13-
private static readonly byte[] PaddingBytes = new byte[4];
14-
15-
/// <summary>
16-
/// A scratch buffer to reduce allocations.
17-
/// </summary>
18-
private readonly byte[] buffer = new byte[4];
19-
2013
/// <summary>
2114
/// Initializes a new instance of the <see cref="TiffStreamWriter"/> class.
2215
/// </summary>
@@ -41,11 +34,12 @@ internal sealed class TiffStreamWriter : IDisposable
4134
/// <summary>
4235
/// Writes an empty four bytes to the stream, returning the offset to be written later.
4336
/// </summary>
37+
/// <param name="buffer">Scratch buffer with minimum size of 4.</param>
4438
/// <returns>The offset to be written later.</returns>
45-
public long PlaceMarker()
39+
public long PlaceMarker(Span<byte> buffer)
4640
{
4741
long offset = this.BaseStream.Position;
48-
this.Write(0u);
42+
this.Write(0u, buffer);
4943
return offset;
5044
}
5145

@@ -71,36 +65,38 @@ public long PlaceMarker()
7165
/// Writes a two-byte unsigned integer to the current stream.
7266
/// </summary>
7367
/// <param name="value">The two-byte unsigned integer to write.</param>
74-
public void Write(ushort value)
68+
/// <param name="buffer">Scratch buffer with minimum size of 2.</param>
69+
public void Write(ushort value, Span<byte> buffer)
7570
{
7671
if (IsLittleEndian)
7772
{
78-
BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, value);
73+
BinaryPrimitives.WriteUInt16LittleEndian(buffer, value);
7974
}
8075
else
8176
{
82-
BinaryPrimitives.WriteUInt16BigEndian(this.buffer, value);
77+
BinaryPrimitives.WriteUInt16BigEndian(buffer, value);
8378
}
8479

85-
this.BaseStream.Write(this.buffer.AsSpan(0, 2));
80+
this.BaseStream.Write(buffer.Slice(0, 2));
8681
}
8782

8883
/// <summary>
8984
/// Writes a four-byte unsigned integer to the current stream.
9085
/// </summary>
9186
/// <param name="value">The four-byte unsigned integer to write.</param>
92-
public void Write(uint value)
87+
/// <param name="buffer">Scratch buffer with minimum size of 4.</param>
88+
public void Write(uint value, Span<byte> buffer)
9389
{
9490
if (IsLittleEndian)
9591
{
96-
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value);
92+
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
9793
}
9894
else
9995
{
100-
BinaryPrimitives.WriteUInt32BigEndian(this.buffer, value);
96+
BinaryPrimitives.WriteUInt32BigEndian(buffer, value);
10197
}
10298

103-
this.BaseStream.Write(this.buffer.AsSpan(0, 4));
99+
this.BaseStream.Write(buffer.Slice(0, 4));
104100
}
105101

106102
/// <summary>
@@ -113,7 +109,10 @@ public void WritePadded(Span<byte> value)
113109

114110
if (value.Length % 4 != 0)
115111
{
116-
this.BaseStream.Write(PaddingBytes, 0, 4 - (value.Length % 4));
112+
// No allocation occurs, refers directly to assembly's data segment.
113+
ReadOnlySpan<byte> paddingBytes = new byte[4] { 0x00, 0x00, 0x00, 0x00 };
114+
paddingBytes = paddingBytes[..(4 - (value.Length % 4))];
115+
this.BaseStream.Write(paddingBytes);
117116
}
118117
}
119118

@@ -122,18 +121,19 @@ public void WritePadded(Span<byte> value)
122121
/// </summary>
123122
/// <param name="offset">The offset returned when placing the marker</param>
124123
/// <param name="value">The four-byte unsigned integer to write.</param>
125-
public void WriteMarker(long offset, uint value)
124+
/// <param name="buffer">Scratch buffer.</param>
125+
public void WriteMarker(long offset, uint value, Span<byte> buffer)
126126
{
127127
long back = this.BaseStream.Position;
128128
this.BaseStream.Seek(offset, SeekOrigin.Begin);
129-
this.Write(value);
129+
this.Write(value, buffer);
130130
this.BaseStream.Seek(back, SeekOrigin.Begin);
131131
}
132132

133-
public void WriteMarkerFast(long offset, uint value)
133+
public void WriteMarkerFast(long offset, uint value, Span<byte> buffer)
134134
{
135135
this.BaseStream.Seek(offset, SeekOrigin.Begin);
136-
this.Write(value);
136+
this.Write(value, buffer);
137137
}
138138

139139
/// <summary>

0 commit comments

Comments
 (0)