Skip to content

Commit 07ec651

Browse files
Removes usage of linq in several critical paths (#918)
* Remove linq usage from jpeg + formatting * png * ICC + formattiing * Resize * Fix base class comparison.
1 parent efcbe14 commit 07ec651

26 files changed

+122
-263
lines changed

src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
1212
internal static class ProfileResolver
1313
{
1414
/// <summary>
15-
/// Describes the EXIF specific markers.
15+
/// Describes the JFIF specific markers.
1616
/// </summary>
1717
public static readonly byte[] JFifMarker = Encoding.ASCII.GetBytes("JFIF\0");
1818

1919
/// <summary>
20-
/// Describes the EXIF specific markers.
20+
/// Describes the ICC specific markers.
2121
/// </summary>
2222
public static readonly byte[] IccMarker = Encoding.ASCII.GetBytes("ICC_PROFILE\0");
2323

2424
/// <summary>
25-
/// Describes the ICC specific markers.
25+
/// Describes the EXIF specific markers.
2626
/// </summary>
2727
public static readonly byte[] ExifMarker = Encoding.ASCII.GetBytes("Exif\0\0");
2828

@@ -36,7 +36,7 @@ internal static class ProfileResolver
3636
/// </summary>
3737
/// <param name="bytesToCheck">The bytes to check.</param>
3838
/// <param name="profileIdentifier">The profile identifier.</param>
39-
/// <returns>The <see cref="bool"/></returns>
39+
/// <returns>The <see cref="bool"/>.</returns>
4040
public static bool IsProfile(ReadOnlySpan<byte> bytesToCheck, ReadOnlySpan<byte> profileIdentifier)
4141
{
4242
return bytesToCheck.Length >= profileIdentifier.Length

src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System;
55
using System.Buffers.Binary;
66
using System.IO;
7-
using System.Linq;
87
using System.Runtime.CompilerServices;
98
using System.Runtime.InteropServices;
109
using SixLabors.ImageSharp.Common.Helpers;
@@ -726,7 +725,7 @@ private void ProcessStartOfFrameMarker(int remaining, in JpegFileMarker frameMar
726725
this.InputStream.Read(this.temp, 0, length);
727726

728727
// We only support 8-bit and 12-bit precision.
729-
if (!this.supportedPrecisions.Contains(this.temp[0]))
728+
if (Array.IndexOf(this.supportedPrecisions, this.temp[0]) == -1)
730729
{
731730
JpegThrowHelper.ThrowImageFormatException("Only 8-Bit and 12-Bit precision supported.");
732731
}

src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System;
55
using System.Buffers.Binary;
66
using System.IO;
7-
using System.Linq;
87
using System.Runtime.CompilerServices;
98
using SixLabors.ImageSharp.Common.Helpers;
109
using SixLabors.ImageSharp.Formats.Jpeg.Components;
@@ -197,7 +196,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
197196
Guard.NotNull(image, nameof(image));
198197
Guard.NotNull(stream, nameof(stream));
199198

200-
ushort max = JpegConstants.MaxLength;
199+
const ushort max = JpegConstants.MaxLength;
201200
if (image.Width >= max || image.Height >= max)
202201
{
203202
throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}.");
@@ -226,7 +225,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
226225
InitQuantizationTable(1, scale, ref this.chrominanceQuantTable);
227226

228227
// Compute number of components based on input image type.
229-
int componentCount = 3;
228+
const int componentCount = 3;
230229

231230
// Write the Start Of Image marker.
232231
this.WriteApplicationHeader(metadata);
@@ -278,7 +277,7 @@ private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref
278277
private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant)
279278
{
280279
DebugGuard.MustBeBetweenOrEqualTo(i, 0, 1, nameof(i));
281-
var unscaledQuant = (i == 0) ? UnscaledQuant_Luminance : UnscaledQuant_Chrominance;
280+
ReadOnlySpan<byte> unscaledQuant = (i == 0) ? UnscaledQuant_Luminance : UnscaledQuant_Chrominance;
282281

283282
for (int j = 0; j < Block8x8F.Size; j++)
284283
{
@@ -653,8 +652,8 @@ private void WriteExifProfile(ExifProfile exifProfile)
653652
return;
654653
}
655654

656-
const int MaxBytesApp1 = 65533;
657-
const int MaxBytesWithExifId = 65527;
655+
const int MaxBytesApp1 = 65533; // 64k - 2 padding bytes
656+
const int MaxBytesWithExifId = 65527; // Max - 6 bytes for EXIF header.
658657

659658
byte[] data = exifProfile?.ToByteArray();
660659

@@ -663,31 +662,30 @@ private void WriteExifProfile(ExifProfile exifProfile)
663662
return;
664663
}
665664

666-
data = ProfileResolver.ExifMarker.Concat(data).ToArray();
667-
668-
int remaining = data.Length;
665+
// We can write up to a maximum of 64 data to the initial marker so calculate boundaries.
666+
int exifMarkerLength = ProfileResolver.ExifMarker.Length;
667+
int remaining = exifMarkerLength + data.Length;
669668
int bytesToWrite = remaining > MaxBytesApp1 ? MaxBytesApp1 : remaining;
670669
int app1Length = bytesToWrite + 2;
671670

671+
// Write the app marker, EXIF marker, and data
672672
this.WriteApp1Header(app1Length);
673-
674-
// write the exif data
675-
this.outputStream.Write(data, 0, bytesToWrite);
673+
this.outputStream.Write(ProfileResolver.ExifMarker);
674+
this.outputStream.Write(data, 0, bytesToWrite - exifMarkerLength);
676675
remaining -= bytesToWrite;
677676

678-
// if the exif data exceeds 64K, write it in multiple APP1 Markers
679-
for (int idx = MaxBytesApp1; idx < data.Length; idx += MaxBytesWithExifId)
677+
// If the exif data exceeds 64K, write it in multiple APP1 Markers
678+
for (int idx = MaxBytesWithExifId; idx < data.Length; idx += MaxBytesWithExifId)
680679
{
681680
bytesToWrite = remaining > MaxBytesWithExifId ? MaxBytesWithExifId : remaining;
682-
app1Length = bytesToWrite + 2 + 6;
681+
app1Length = bytesToWrite + 2 + exifMarkerLength;
683682

684683
this.WriteApp1Header(app1Length);
685684

686-
// write Exif00 marker
687-
ProfileResolver.ExifMarker.AsSpan().CopyTo(this.buffer.AsSpan());
688-
this.outputStream.Write(this.buffer, 0, 6);
685+
// Write Exif00 marker
686+
this.outputStream.Write(ProfileResolver.ExifMarker);
689687

690-
// write the exif data
688+
// Write the exif data
691689
this.outputStream.Write(data, idx, bytesToWrite);
692690

693691
remaining -= bytesToWrite;

src/ImageSharp/Formats/Png/PngEncoderCore.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using System.Buffers.Binary;
77
using System.Collections.Generic;
88
using System.IO;
9-
using System.Linq;
109
using System.Runtime.CompilerServices;
1110
using System.Runtime.InteropServices;
1211
using SixLabors.ImageSharp.Advanced;
@@ -231,7 +230,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
231230
if (this.pngColorType == PngColorType.Palette)
232231
{
233232
byte bits = (byte)this.pngBitDepth;
234-
if (!ColorTypes[this.pngColorType.Value].Contains(bits))
233+
if (Array.IndexOf(ColorTypes[this.pngColorType.Value], bits) == -1)
235234
{
236235
throw new NotSupportedException("Bit depth is not supported or not valid.");
237236
}
@@ -268,7 +267,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
268267
else
269268
{
270269
this.bitDepth = (byte)this.pngBitDepth;
271-
if (!ColorTypes[this.pngColorType.Value].Contains(this.bitDepth))
270+
if (Array.IndexOf(ColorTypes[this.pngColorType.Value], this.bitDepth) == -1)
272271
{
273272
throw new NotSupportedException("Bit depth is not supported or not valid.");
274273
}

src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5-
using System.Linq;
65

76
namespace SixLabors.ImageSharp.Metadata.Profiles.Icc
87
{
@@ -52,7 +51,7 @@ public bool Equals(IccOneDimensionalCurve other)
5251
}
5352

5453
return this.BreakPoints.AsSpan().SequenceEqual(other.BreakPoints)
55-
&& this.Segments.SequenceEqual(other.Segments);
54+
&& this.Segments.AsSpan().SequenceEqual(other.Segments);
5655
}
5756
}
5857
}

src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5-
using System.Linq;
65
using System.Numerics;
76

87
namespace SixLabors.ImageSharp.Metadata.Profiles.Icc
@@ -65,10 +64,7 @@ public bool Equals(IccResponseCurve other)
6564
}
6665

6766
/// <inheritdoc />
68-
public override bool Equals(object obj)
69-
{
70-
return obj is IccResponseCurve other && this.Equals(other);
71-
}
67+
public override bool Equals(object obj) => obj is IccResponseCurve other && this.Equals(other);
7268

7369
/// <inheritdoc />
7470
public override int GetHashCode()
@@ -88,7 +84,7 @@ private bool EqualsResponseArray(IccResponseCurve other)
8884

8985
for (int i = 0; i < this.ResponseArrays.Length; i++)
9086
{
91-
if (!this.ResponseArrays[i].SequenceEqual(other.ResponseArrays[i]))
87+
if (!this.ResponseArrays[i].AsSpan().SequenceEqual(other.ResponseArrays[i]))
9288
{
9389
return false;
9490
}

src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs

Lines changed: 11 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,7 @@ public int WriteTagDataEntryHeader(IccTypeSignature signature)
163163
/// </summary>
164164
/// <param name="value">The entry to write</param>
165165
/// <returns>The number of bytes written</returns>
166-
public int WriteUnknownTagDataEntry(IccUnknownTagDataEntry value)
167-
{
168-
return this.WriteArray(value.Data);
169-
}
166+
public int WriteUnknownTagDataEntry(IccUnknownTagDataEntry value) => this.WriteArray(value.Data);
170167

171168
/// <summary>
172169
/// Writes a <see cref="IccChromaticityTagDataEntry"/>
@@ -269,10 +266,7 @@ public int WriteDataTagDataEntry(IccDataTagDataEntry value)
269266
/// </summary>
270267
/// <param name="value">The entry to write</param>
271268
/// <returns>The number of bytes written</returns>
272-
public int WriteDateTimeTagDataEntry(IccDateTimeTagDataEntry value)
273-
{
274-
return this.WriteDateTime(value.Value);
275-
}
269+
public int WriteDateTimeTagDataEntry(IccDateTimeTagDataEntry value) => this.WriteDateTime(value.Value);
276270

277271
/// <summary>
278272
/// Writes a <see cref="IccLut16TagDataEntry"/>
@@ -563,6 +557,7 @@ public int WriteMultiLocalizedUnicodeTagDataEntry(IccMultiLocalizedUnicodeTagDat
563557
long tpos = this.dataStream.Position;
564558
this.dataStream.Position += cultureCount * 12;
565559

560+
// TODO: Investigate cost of Linq GroupBy
566561
IGrouping<string, IccLocalizedString>[] texts = value.Texts.GroupBy(t => t.Text).ToArray();
567562

568563
uint[] offset = new uint[texts.Length];
@@ -625,7 +620,7 @@ public int WriteMultiProcessElementsTagDataEntry(IccMultiProcessElementsTagDataE
625620
long tpos = this.dataStream.Position;
626621
this.dataStream.Position += value.Data.Length * 8;
627622

628-
IccPositionNumber[] posTable = new IccPositionNumber[value.Data.Length];
623+
var posTable = new IccPositionNumber[value.Data.Length];
629624
for (int i = 0; i < value.Data.Length; i++)
630625
{
631626
uint offset = (uint)(this.dataStream.Position - start);
@@ -673,10 +668,7 @@ public int WriteNamedColor2TagDataEntry(IccNamedColor2TagDataEntry value)
673668
/// </summary>
674669
/// <param name="value">The entry to write</param>
675670
/// <returns>The number of bytes written</returns>
676-
public int WriteParametricCurveTagDataEntry(IccParametricCurveTagDataEntry value)
677-
{
678-
return this.WriteParametricCurve(value.Curve);
679-
}
671+
public int WriteParametricCurveTagDataEntry(IccParametricCurveTagDataEntry value) => this.WriteParametricCurve(value.Curve);
680672

681673
/// <summary>
682674
/// Writes a <see cref="IccProfileSequenceDescTagDataEntry"/>
@@ -793,20 +785,14 @@ public int WriteFix16ArrayTagDataEntry(IccFix16ArrayTagDataEntry value)
793785
/// </summary>
794786
/// <param name="value">The entry to write</param>
795787
/// <returns>The number of bytes written</returns>
796-
public int WriteSignatureTagDataEntry(IccSignatureTagDataEntry value)
797-
{
798-
return this.WriteAsciiString(value.SignatureData, 4, false);
799-
}
788+
public int WriteSignatureTagDataEntry(IccSignatureTagDataEntry value) => this.WriteAsciiString(value.SignatureData, 4, false);
800789

801790
/// <summary>
802791
/// Writes a <see cref="IccTextTagDataEntry"/>
803792
/// </summary>
804793
/// <param name="value">The entry to write</param>
805794
/// <returns>The number of bytes written</returns>
806-
public int WriteTextTagDataEntry(IccTextTagDataEntry value)
807-
{
808-
return this.WriteAsciiString(value.Text);
809-
}
795+
public int WriteTextTagDataEntry(IccTextTagDataEntry value) => this.WriteAsciiString(value.Text);
810796

811797
/// <summary>
812798
/// Writes a <see cref="IccUFix16ArrayTagDataEntry"/>
@@ -829,40 +815,28 @@ public int WriteUFix16ArrayTagDataEntry(IccUFix16ArrayTagDataEntry value)
829815
/// </summary>
830816
/// <param name="value">The entry to write</param>
831817
/// <returns>The number of bytes written</returns>
832-
public int WriteUInt16ArrayTagDataEntry(IccUInt16ArrayTagDataEntry value)
833-
{
834-
return this.WriteArray(value.Data);
835-
}
818+
public int WriteUInt16ArrayTagDataEntry(IccUInt16ArrayTagDataEntry value) => this.WriteArray(value.Data);
836819

837820
/// <summary>
838821
/// Writes a <see cref="IccUInt32ArrayTagDataEntry"/>
839822
/// </summary>
840823
/// <param name="value">The entry to write</param>
841824
/// <returns>The number of bytes written</returns>
842-
public int WriteUInt32ArrayTagDataEntry(IccUInt32ArrayTagDataEntry value)
843-
{
844-
return this.WriteArray(value.Data);
845-
}
825+
public int WriteUInt32ArrayTagDataEntry(IccUInt32ArrayTagDataEntry value) => this.WriteArray(value.Data);
846826

847827
/// <summary>
848828
/// Writes a <see cref="IccUInt64ArrayTagDataEntry"/>
849829
/// </summary>
850830
/// <param name="value">The entry to write</param>
851831
/// <returns>The number of bytes written</returns>
852-
public int WriteUInt64ArrayTagDataEntry(IccUInt64ArrayTagDataEntry value)
853-
{
854-
return this.WriteArray(value.Data);
855-
}
832+
public int WriteUInt64ArrayTagDataEntry(IccUInt64ArrayTagDataEntry value) => this.WriteArray(value.Data);
856833

857834
/// <summary>
858835
/// Writes a <see cref="IccUInt8ArrayTagDataEntry"/>
859836
/// </summary>
860837
/// <param name="value">The entry to write</param>
861838
/// <returns>The number of bytes written</returns>
862-
public int WriteUInt8ArrayTagDataEntry(IccUInt8ArrayTagDataEntry value)
863-
{
864-
return this.WriteArray(value.Data);
865-
}
839+
public int WriteUInt8ArrayTagDataEntry(IccUInt8ArrayTagDataEntry value) => this.WriteArray(value.Data);
866840

867841
/// <summary>
868842
/// Writes a <see cref="IccViewingConditionsTagDataEntry"/>

src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ private void WriteTagTable(IccDataWriter writer, IccTagTableEntry[] table)
7070

7171
private IccTagTableEntry[] WriteTagData(IccDataWriter writer, IccTagDataEntry[] entries)
7272
{
73+
// TODO: Investigate cost of Linq GroupBy
7374
IEnumerable<IGrouping<IccTagDataEntry, IccTagDataEntry>> grouped = entries.GroupBy(t => t);
7475

7576
// (Header size) + (entry count) + (nr of entries) * (size of table entry)

src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5-
using System.Linq;
65

76
namespace SixLabors.ImageSharp.Metadata.Profiles.Icc
87
{
@@ -17,9 +16,7 @@ internal sealed class IccCurveSetProcessElement : IccMultiProcessElement, IEquat
1716
/// <param name="curves">An array with one dimensional curves</param>
1817
public IccCurveSetProcessElement(IccOneDimensionalCurve[] curves)
1918
: base(IccMultiProcessElementSignature.CurveSet, curves?.Length ?? 1, curves?.Length ?? 1)
20-
{
21-
this.Curves = curves ?? throw new ArgumentNullException(nameof(curves));
22-
}
19+
=> this.Curves = curves ?? throw new ArgumentNullException(nameof(curves));
2320

2421
/// <summary>
2522
/// Gets an array of one dimensional curves
@@ -31,16 +28,13 @@ public override bool Equals(IccMultiProcessElement other)
3128
{
3229
if (base.Equals(other) && other is IccCurveSetProcessElement element)
3330
{
34-
return this.Curves.SequenceEqual(element.Curves);
31+
return this.Curves.AsSpan().SequenceEqual(element.Curves);
3532
}
3633

3734
return false;
3835
}
3936

4037
/// <inheritdoc />
41-
public bool Equals(IccCurveSetProcessElement other)
42-
{
43-
return this.Equals((IccMultiProcessElement)other);
44-
}
38+
public bool Equals(IccCurveSetProcessElement other) => this.Equals((IccMultiProcessElement)other);
4539
}
4640
}

0 commit comments

Comments
 (0)