Skip to content

Commit c6d1ac7

Browse files
Merge branch 'main' into js/accumulative-memory-limit
2 parents 56fc139 + ec79a2f commit c6d1ac7

31 files changed

Lines changed: 1495 additions & 549 deletions

.github/workflows/build-and-test.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
run: echo "lfs_key=$LFS_KEY" >> "$GITHUB_OUTPUT"
5050

5151
- name: Git Setup LFS Cache
52-
uses: actions/cache@v4
52+
uses: actions/cache@v5
5353
with:
5454
path: .git/lfs
5555
key: ${{ steps.expose-key.outputs.lfs_key }}
@@ -144,7 +144,7 @@ jobs:
144144

145145
# Use the warmed key from WarmLFS. Do not recompute or recreate .lfs-assets-id here.
146146
- name: Git Setup LFS Cache
147-
uses: actions/cache@v4
147+
uses: actions/cache@v5
148148
with:
149149
path: .git/lfs
150150
key: ${{ needs.WarmLFS.outputs.lfs_key }}
@@ -157,7 +157,7 @@ jobs:
157157
uses: NuGet/setup-nuget@v2
158158

159159
- name: NuGet Setup Cache
160-
uses: actions/cache@v4
160+
uses: actions/cache@v5
161161
id: nuget-cache
162162
with:
163163
path: ~/.nuget
@@ -166,14 +166,14 @@ jobs:
166166

167167
- name: DotNet Setup
168168
if: ${{ matrix.options.sdk-preview != true }}
169-
uses: actions/setup-dotnet@v4
169+
uses: actions/setup-dotnet@v5
170170
with:
171171
dotnet-version: |
172172
8.0.x
173173
174174
- name: DotNet Setup Preview
175175
if: ${{ matrix.options.sdk-preview == true }}
176-
uses: actions/setup-dotnet@v4
176+
uses: actions/setup-dotnet@v5
177177
with:
178178
dotnet-version: |
179179
10.0.x
@@ -209,7 +209,7 @@ jobs:
209209
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
210210

211211
- name: Export Failed Output
212-
uses: actions/upload-artifact@v4
212+
uses: actions/upload-artifact@v6
213213
if: failure()
214214
with:
215215
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip
@@ -236,7 +236,7 @@ jobs:
236236
uses: NuGet/setup-nuget@v2
237237

238238
- name: NuGet Setup Cache
239-
uses: actions/cache@v4
239+
uses: actions/cache@v5
240240
id: nuget-cache
241241
with:
242242
path: ~/.nuget

.github/workflows/code-coverage.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
run: git lfs ls-files -l | awk '{print $1}' | sort > .lfs-assets-id
4747

4848
- name: Git Setup LFS Cache
49-
uses: actions/cache@v4
49+
uses: actions/cache@v5
5050
id: lfs-cache
5151
with:
5252
path: .git/lfs
@@ -59,15 +59,15 @@ jobs:
5959
uses: NuGet/setup-nuget@v2
6060

6161
- name: NuGet Setup Cache
62-
uses: actions/cache@v4
62+
uses: actions/cache@v5
6363
id: nuget-cache
6464
with:
6565
path: ~/.nuget
6666
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }}
6767
restore-keys: ${{ runner.os }}-nuget-
6868

6969
- name: DotNet Setup
70-
uses: actions/setup-dotnet@v4
70+
uses: actions/setup-dotnet@v5
7171
with:
7272
dotnet-version: |
7373
8.0.x
@@ -86,7 +86,7 @@ jobs:
8686
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
8787

8888
- name: Export Failed Output
89-
uses: actions/upload-artifact@v4
89+
uses: actions/upload-artifact@v6
9090
if: failure()
9191
with:
9292
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip

src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,30 +131,35 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
131131
try
132132
{
133133
int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette);
134+
ushort bitsPerPixel = this.infoHeader.BitsPerPixel;
134135

135136
image = new Image<TPixel>(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata);
136137

137138
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
138139

139140
switch (this.infoHeader.Compression)
140141
{
141-
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32 && this.bmpMetadata.InfoHeaderType is BmpInfoHeaderType.WinVersion3:
142+
case BmpCompression.RGB when bitsPerPixel is 32 && this.bmpMetadata.InfoHeaderType is BmpInfoHeaderType.WinVersion3:
142143
this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
143144

144145
break;
145-
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32:
146+
147+
case BmpCompression.RGB when bitsPerPixel is 32:
146148
this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
147149

148150
break;
149-
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 24:
151+
152+
case BmpCompression.RGB when bitsPerPixel is 24:
150153
this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
151154

152155
break;
153-
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 16:
156+
157+
case BmpCompression.RGB when bitsPerPixel is 16:
154158
this.ReadRgb16(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
155159

156160
break;
157-
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8 && this.processedAlphaMask:
161+
162+
case BmpCompression.RGB when bitsPerPixel is > 0 and <= 8 && this.processedAlphaMask:
158163
this.ReadRgbPaletteWithAlphaMask(
159164
stream,
160165
pixels,
@@ -166,7 +171,8 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
166171
inverted);
167172

168173
break;
169-
case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8:
174+
175+
case BmpCompression.RGB when bitsPerPixel is > 0 and <= 8:
170176
this.ReadRgbPalette(
171177
stream,
172178
pixels,
@@ -179,6 +185,10 @@ protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Cance
179185

180186
break;
181187

188+
case BmpCompression.RGB when bitsPerPixel is <= 0 or > 32:
189+
BmpThrowHelper.ThrowInvalidImageContentException($"Invalid bits per pixel: {bitsPerPixel}");
190+
break;
191+
182192
case BmpCompression.RLE24:
183193
this.ReadRle24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
184194

src/ImageSharp/Image.LoadPixelData.cs

Lines changed: 109 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Six Labors Split License.
33

44
using System.Runtime.InteropServices;
5-
using SixLabors.ImageSharp.Memory;
65
using SixLabors.ImageSharp.PixelFormats;
76

87
namespace SixLabors.ImageSharp;
@@ -25,6 +24,27 @@ public static Image<TPixel> LoadPixelData<TPixel>(ReadOnlySpan<TPixel> data, int
2524
where TPixel : unmanaged, IPixel<TPixel>
2625
=> LoadPixelData(Configuration.Default, data, width, height);
2726

27+
/// <summary>
28+
/// Create a new instance of the <see cref="Image{TPixel}"/> class from raw <typeparamref name="TPixel"/> data
29+
/// using <paramref name="rowStride"/> pixels between source row starts.
30+
/// </summary>
31+
/// <param name="data">The readonly span containing image data.</param>
32+
/// <param name="width">The width of the final image.</param>
33+
/// <param name="height">The height of the final image.</param>
34+
/// <param name="rowStride">The number of pixels between row starts in <paramref name="data"/>.</param>
35+
/// <typeparam name="TPixel">The pixel format.</typeparam>
36+
/// <exception cref="ArgumentOutOfRangeException">
37+
/// <paramref name="width"/> or <paramref name="height"/> is not positive,
38+
/// or <paramref name="rowStride"/> is less than <paramref name="width"/>.
39+
/// </exception>
40+
/// <exception cref="ArgumentException">
41+
/// <paramref name="data"/> is smaller than <c>((height - 1) * rowStride) + width</c>.
42+
/// </exception>
43+
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
44+
public static Image<TPixel> LoadPixelData<TPixel>(ReadOnlySpan<TPixel> data, int width, int height, int rowStride)
45+
where TPixel : unmanaged, IPixel<TPixel>
46+
=> LoadPixelData(Configuration.Default, data, width, height, rowStride);
47+
2848
/// <summary>
2949
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given readonly span of bytes in <typeparamref name="TPixel"/> format.
3050
/// </summary>
@@ -38,6 +58,28 @@ public static Image<TPixel> LoadPixelData<TPixel>(ReadOnlySpan<byte> data, int w
3858
where TPixel : unmanaged, IPixel<TPixel>
3959
=> LoadPixelData<TPixel>(Configuration.Default, data, width, height);
4060

61+
/// <summary>
62+
/// Create a new instance of the <see cref="Image{TPixel}"/> class from a readonly span of bytes in
63+
/// <typeparamref name="TPixel"/> format using <paramref name="rowStrideInBytes"/> bytes between source row starts.
64+
/// </summary>
65+
/// <param name="data">The readonly span containing image data.</param>
66+
/// <param name="width">The width of the final image.</param>
67+
/// <param name="height">The height of the final image.</param>
68+
/// <param name="rowStrideInBytes">The number of bytes between row starts in <paramref name="data"/>.</param>
69+
/// <typeparam name="TPixel">The pixel format.</typeparam>
70+
/// <exception cref="ArgumentOutOfRangeException">
71+
/// <paramref name="width"/> or <paramref name="height"/> is not positive,
72+
/// or <paramref name="rowStrideInBytes"/> resolves to fewer than <paramref name="width"/> pixels.
73+
/// </exception>
74+
/// <exception cref="ArgumentException">
75+
/// <paramref name="rowStrideInBytes"/> is not divisible by the pixel size,
76+
/// or <paramref name="data"/> is smaller than the required strided image length.
77+
/// </exception>
78+
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
79+
public static Image<TPixel> LoadPixelData<TPixel>(ReadOnlySpan<byte> data, int width, int height, int rowStrideInBytes)
80+
where TPixel : unmanaged, IPixel<TPixel>
81+
=> LoadPixelData<TPixel>(Configuration.Default, data, width, height, rowStrideInBytes);
82+
4183
/// <summary>
4284
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given readonly span of bytes in <typeparamref name="TPixel"/> format.
4385
/// </summary>
@@ -53,6 +95,40 @@ public static Image<TPixel> LoadPixelData<TPixel>(Configuration configuration, R
5395
where TPixel : unmanaged, IPixel<TPixel>
5496
=> LoadPixelData(configuration, MemoryMarshal.Cast<byte, TPixel>(data), width, height);
5597

98+
/// <summary>
99+
/// Create a new instance of the <see cref="Image{TPixel}"/> class from a readonly span of bytes in
100+
/// <typeparamref name="TPixel"/> format using <paramref name="rowStrideInBytes"/> bytes between source row starts.
101+
/// </summary>
102+
/// <param name="configuration">The configuration for the decoder.</param>
103+
/// <param name="data">The readonly span containing image data.</param>
104+
/// <param name="width">The width of the final image.</param>
105+
/// <param name="height">The height of the final image.</param>
106+
/// <param name="rowStrideInBytes">The number of bytes between row starts in <paramref name="data"/>.</param>
107+
/// <typeparam name="TPixel">The pixel format.</typeparam>
108+
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
109+
/// <exception cref="ArgumentOutOfRangeException">
110+
/// <paramref name="width"/> or <paramref name="height"/> is not positive,
111+
/// or <paramref name="rowStrideInBytes"/> resolves to fewer than <paramref name="width"/> pixels.
112+
/// </exception>
113+
/// <exception cref="ArgumentException">
114+
/// <paramref name="rowStrideInBytes"/> is not divisible by the pixel size,
115+
/// or <paramref name="data"/> is smaller than the required strided image length.
116+
/// </exception>
117+
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
118+
public static Image<TPixel> LoadPixelData<TPixel>(
119+
Configuration configuration,
120+
ReadOnlySpan<byte> data,
121+
int width,
122+
int height,
123+
int rowStrideInBytes)
124+
where TPixel : unmanaged, IPixel<TPixel>
125+
{
126+
Guard.NotNull(configuration, nameof(configuration));
127+
128+
int rowStride = GetPixelRowStrideFromByteStride<TPixel>(width, rowStrideInBytes, nameof(rowStrideInBytes));
129+
return LoadPixelData(configuration, MemoryMarshal.Cast<byte, TPixel>(data), width, height, rowStride);
130+
}
131+
56132
/// <summary>
57133
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the raw <typeparamref name="TPixel"/> data.
58134
/// </summary>
@@ -66,20 +142,42 @@ public static Image<TPixel> LoadPixelData<TPixel>(Configuration configuration, R
66142
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
67143
public static Image<TPixel> LoadPixelData<TPixel>(Configuration configuration, ReadOnlySpan<TPixel> data, int width, int height)
68144
where TPixel : unmanaged, IPixel<TPixel>
145+
=> LoadPixelData(configuration, data, width, height, width);
146+
147+
/// <summary>
148+
/// Create a new instance of the <see cref="Image{TPixel}"/> class from raw <typeparamref name="TPixel"/> data
149+
/// using <paramref name="rowStride"/> pixels between source row starts.
150+
/// </summary>
151+
/// <param name="configuration">The configuration for the decoder.</param>
152+
/// <param name="data">The readonly span containing the image pixel data.</param>
153+
/// <param name="width">The width of the final image.</param>
154+
/// <param name="height">The height of the final image.</param>
155+
/// <param name="rowStride">The number of pixels between row starts in <paramref name="data"/>.</param>
156+
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
157+
/// <exception cref="ArgumentOutOfRangeException">
158+
/// <paramref name="width"/> or <paramref name="height"/> is not positive,
159+
/// or <paramref name="rowStride"/> is less than <paramref name="width"/>.
160+
/// </exception>
161+
/// <exception cref="ArgumentException">
162+
/// <paramref name="data"/> is smaller than <c>((height - 1) * rowStride) + width</c>.
163+
/// </exception>
164+
/// <typeparam name="TPixel">The pixel format.</typeparam>
165+
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
166+
public static Image<TPixel> LoadPixelData<TPixel>(
167+
Configuration configuration,
168+
ReadOnlySpan<TPixel> data,
169+
int width,
170+
int height,
171+
int rowStride)
172+
where TPixel : unmanaged, IPixel<TPixel>
69173
{
70174
Guard.NotNull(configuration, nameof(configuration));
71-
72-
if (data.IsEmpty)
73-
{
74-
throw new ArgumentException("Pixel data cannot be empty.", nameof(data));
75-
}
76-
77-
int count = width * height;
78-
Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data));
175+
ValidateWrapMemoryStride(width, height, rowStride, nameof(rowStride));
176+
long requiredLength = GetRequiredLength(width, height, rowStride);
177+
Guard.MustBeGreaterThanOrEqualTo(data.Length, requiredLength, nameof(data));
79178

80179
Image<TPixel> image = new(configuration, width, height);
81-
data = data[..count];
82-
data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup);
180+
image.Frames.RootFrame.PixelBuffer.CopyFrom(data, rowStride);
83181

84182
return image;
85183
}

0 commit comments

Comments
 (0)