Skip to content

Commit bf97398

Browse files
Merge branch 'main' into bp/tiff-jpeg-cmyk
2 parents 916acb7 + 63d1d2c commit bf97398

File tree

86 files changed

+1403
-372
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+1403
-372
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ on:
99
pull_request:
1010
branches:
1111
- main
12+
- release/*
1213
types: [ labeled, opened, synchronize, reopened ]
1314
jobs:
1415
Build:
@@ -75,7 +76,7 @@ jobs:
7576
git config --global core.longpaths true
7677
7778
- name: Git Checkout
78-
uses: actions/checkout@v3
79+
uses: actions/checkout@v4
7980
with:
8081
fetch-depth: 0
8182
submodules: recursive
@@ -171,7 +172,7 @@ jobs:
171172
git config --global core.longpaths true
172173
173174
- name: Git Checkout
174-
uses: actions/checkout@v3
175+
uses: actions/checkout@v4
175176
with:
176177
fetch-depth: 0
177178
submodules: recursive

.github/workflows/code-coverage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
git config --global core.longpaths true
2525
2626
- name: Git Checkout
27-
uses: actions/checkout@v3
27+
uses: actions/checkout@v4
2828
with:
2929
fetch-depth: 0
3030
submodules: recursive

ImageSharp.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1
1818
Directory.Build.targets = Directory.Build.targets
1919
LICENSE = LICENSE
2020
README.md = README.md
21+
SixLabors.ImageSharp.props = SixLabors.ImageSharp.props
2122
EndProjectSection
2223
EndProject
2324
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{1799C43E-5C54-4A8F-8D64-B1475241DB0D}"

SixLabors.ImageSharp.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Project>
33

44
<!--Add common namespaces to implicit global usings if enabled.-->
5-
<ItemGroup Condition="'$(ImplicitUsings)'=='enable' OR '$(ImplicitUsings)'=='true'">
5+
<ItemGroup Condition="'$(UseImageSharp)'=='enable' OR '$(UseImageSharp)'=='true'">
66
<Using Include="SixLabors.ImageSharp" />
77
<Using Include="SixLabors.ImageSharp.PixelFormats" />
88
<Using Include="SixLabors.ImageSharp.Processing" />

src/ImageSharp/Advanced/ParallelRowIterator.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public static void IterateRows<T>(
5050
int width = rectangle.Width;
5151
int height = rectangle.Height;
5252

53-
int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
53+
int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
5454
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
5555

5656
// Avoid TPL overhead in this trivial case:
@@ -115,7 +115,7 @@ public static void IterateRows<T, TBuffer>(
115115
int width = rectangle.Width;
116116
int height = rectangle.Height;
117117

118-
int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
118+
int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
119119
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
120120
MemoryAllocator allocator = parallelSettings.MemoryAllocator;
121121
int bufferLength = Unsafe.AsRef(operation).GetRequiredBufferLength(rectangle);
@@ -180,7 +180,7 @@ public static void IterateRowIntervals<T>(
180180
int width = rectangle.Width;
181181
int height = rectangle.Height;
182182

183-
int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
183+
int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
184184
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
185185

186186
// Avoid TPL overhead in this trivial case:
@@ -242,7 +242,7 @@ public static void IterateRowIntervals<T, TBuffer>(
242242
int width = rectangle.Width;
243243
int height = rectangle.Height;
244244

245-
int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
245+
int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
246246
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
247247
MemoryAllocator allocator = parallelSettings.MemoryAllocator;
248248
int bufferLength = Unsafe.AsRef(operation).GetRequiredBufferLength(rectangle);
@@ -270,7 +270,7 @@ public static void IterateRowIntervals<T, TBuffer>(
270270
}
271271

272272
[MethodImpl(InliningOptions.ShortMethod)]
273-
private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor);
273+
private static int DivideCeil(long dividend, int divisor) => (int)Math.Min(1 + ((dividend - 1) / divisor), int.MaxValue);
274274

275275
private static void ValidateRectangle(Rectangle rectangle)
276276
{

src/ImageSharp/Color/Color.Conversions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public Color(Vector4 vector)
139139
/// </summary>
140140
/// <param name="color">The <see cref="Color"/>.</param>
141141
/// <returns>The <see cref="Vector4"/>.</returns>
142-
public static explicit operator Vector4(Color color) => color.ToVector4();
142+
public static explicit operator Vector4(Color color) => color.ToScaledVector4();
143143

144144
/// <summary>
145145
/// Converts an <see cref="Vector4"/> to <see cref="Color"/>.
@@ -228,7 +228,7 @@ internal Bgr24 ToBgr24()
228228
}
229229

230230
[MethodImpl(InliningOptions.ShortMethod)]
231-
internal Vector4 ToVector4()
231+
internal Vector4 ToScaledVector4()
232232
{
233233
if (this.boxedHighPrecisionPixel is null)
234234
{

src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,33 @@ public static Vector256<float> MultiplyAddNegated(
629629
return Avx.Subtract(c, Avx.Multiply(a, b));
630630
}
631631

632+
/// <summary>
633+
/// Blend packed 8-bit integers from <paramref name="left"/> and <paramref name="right"/> using <paramref name="mask"/>.
634+
/// The high bit of each corresponding <paramref name="mask"/> byte determines the selection.
635+
/// If the high bit is set the element of <paramref name="left"/> is selected.
636+
/// The element of <paramref name="right"/> is selected otherwise.
637+
/// </summary>
638+
/// <param name="left">The left vector.</param>
639+
/// <param name="right">The right vector.</param>
640+
/// <param name="mask">The mask vector.</param>
641+
/// <returns>The <see cref="Vector256{T}"/>.</returns>
642+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
643+
public static Vector128<byte> BlendVariable(Vector128<byte> left, Vector128<byte> right, Vector128<byte> mask)
644+
{
645+
if (Sse41.IsSupported)
646+
{
647+
return Sse41.BlendVariable(left, right, mask);
648+
}
649+
else if (Sse2.IsSupported)
650+
{
651+
return Sse2.Or(Sse2.And(right, mask), Sse2.AndNot(mask, left));
652+
}
653+
654+
// Use a signed shift right to create a mask with the sign bit.
655+
Vector128<short> signedMask = AdvSimd.ShiftRightArithmetic(mask.AsInt16(), 7);
656+
return AdvSimd.BitwiseSelect(signedMask, right.AsInt16(), left.AsInt16()).AsByte();
657+
}
658+
632659
/// <summary>
633660
/// <see cref="ByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder).
634661
/// </summary>

src/ImageSharp/Formats/Bmp/BmpEncoder.cs

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

44
using SixLabors.ImageSharp.Advanced;
5+
using SixLabors.ImageSharp.Processing;
56

67
namespace SixLabors.ImageSharp.Formats.Bmp;
78

@@ -10,6 +11,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp;
1011
/// </summary>
1112
public sealed class BmpEncoder : QuantizingImageEncoder
1213
{
14+
/// <summary>
15+
/// Initializes a new instance of the <see cref="BmpEncoder"/> class.
16+
/// </summary>
17+
public BmpEncoder() => this.Quantizer = KnownQuantizers.Octree;
18+
1319
/// <summary>
1420
/// Gets the number of bits per pixel.
1521
/// </summary>

src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using SixLabors.ImageSharp.Memory;
1010
using SixLabors.ImageSharp.Metadata;
1111
using SixLabors.ImageSharp.PixelFormats;
12+
using SixLabors.ImageSharp.Processing;
1213
using SixLabors.ImageSharp.Processing.Processors.Quantization;
1314

1415
namespace SixLabors.ImageSharp.Formats.Bmp;
@@ -100,7 +101,7 @@ public BmpEncoderCore(BmpEncoder encoder, MemoryAllocator memoryAllocator)
100101
{
101102
this.memoryAllocator = memoryAllocator;
102103
this.bitsPerPixel = encoder.BitsPerPixel;
103-
this.quantizer = encoder.Quantizer;
104+
this.quantizer = encoder.Quantizer ?? KnownQuantizers.Octree;
104105
this.pixelSamplingStrategy = encoder.PixelSamplingStrategy;
105106
this.infoHeaderType = encoder.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3;
106107
}

src/ImageSharp/Formats/Gif/GifDecoderCore.cs

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
2929
/// </summary>
3030
private IMemoryOwner<byte>? globalColorTable;
3131

32+
/// <summary>
33+
/// The current local color table.
34+
/// </summary>
35+
private IMemoryOwner<byte>? currentLocalColorTable;
36+
37+
/// <summary>
38+
/// Gets the size in bytes of the current local color table.
39+
/// </summary>
40+
private int currentLocalColorTableSize;
41+
3242
/// <summary>
3343
/// The area to restore.
3444
/// </summary>
@@ -159,6 +169,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
159169
finally
160170
{
161171
this.globalColorTable?.Dispose();
172+
this.currentLocalColorTable?.Dispose();
162173
}
163174

164175
if (image is null)
@@ -229,6 +240,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
229240
finally
230241
{
231242
this.globalColorTable?.Dispose();
243+
this.currentLocalColorTable?.Dispose();
232244
}
233245

234246
if (this.logicalScreenDescriptor.Width == 0 && this.logicalScreenDescriptor.Height == 0)
@@ -332,7 +344,7 @@ private void ReadApplicationExtension(BufferedReadStream stream)
332344
if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize)
333345
{
334346
stream.Read(this.buffer.Span, 0, GifConstants.NetscapeLoopingSubBlockSize);
335-
this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.Span.Slice(1)).RepeatCount;
347+
this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.Span[1..]).RepeatCount;
336348
stream.Skip(1); // Skip the terminator.
337349
return;
338350
}
@@ -415,25 +427,27 @@ private void ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
415427
{
416428
this.ReadImageDescriptor(stream);
417429

418-
IMemoryOwner<byte>? localColorTable = null;
419430
Buffer2D<byte>? indices = null;
420431
try
421432
{
422433
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
423-
if (this.imageDescriptor.LocalColorTableFlag)
434+
bool hasLocalColorTable = this.imageDescriptor.LocalColorTableFlag;
435+
436+
if (hasLocalColorTable)
424437
{
425-
int length = this.imageDescriptor.LocalColorTableSize * 3;
426-
localColorTable = this.configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
427-
stream.Read(localColorTable.GetSpan());
438+
// Read and store the local color table. We allocate the maximum possible size and slice to match.
439+
int length = this.currentLocalColorTableSize = this.imageDescriptor.LocalColorTableSize * 3;
440+
this.currentLocalColorTable ??= this.configuration.MemoryAllocator.Allocate<byte>(768, AllocationOptions.Clean);
441+
stream.Read(this.currentLocalColorTable.GetSpan()[..length]);
428442
}
429443

430444
indices = this.configuration.MemoryAllocator.Allocate2D<byte>(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean);
431445
this.ReadFrameIndices(stream, indices);
432446

433447
Span<byte> rawColorTable = default;
434-
if (localColorTable != null)
448+
if (hasLocalColorTable)
435449
{
436-
rawColorTable = localColorTable.GetSpan();
450+
rawColorTable = this.currentLocalColorTable!.GetSpan()[..this.currentLocalColorTableSize];
437451
}
438452
else if (this.globalColorTable != null)
439453
{
@@ -448,7 +462,6 @@ private void ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? ima
448462
}
449463
finally
450464
{
451-
localColorTable?.Dispose();
452465
indices?.Dispose();
453466
}
454467
}
@@ -509,7 +522,10 @@ private void ReadFrameColors<TPixel>(ref Image<TPixel>? image, ref ImageFrame<TP
509522
prevFrame = previousFrame;
510523
}
511524

512-
currentFrame = image!.Frames.CreateFrame();
525+
// We create a clone of the frame and add it.
526+
// We will overpaint the difference of pixels on the current frame to create a complete image.
527+
// This ensures that we have enough pixel data to process without distortion. #2450
528+
currentFrame = image!.Frames.AddFrame(previousFrame);
513529

514530
this.SetFrameMetadata(currentFrame.Metadata);
515531

@@ -631,7 +647,10 @@ private void ReadFrameMetadata(BufferedReadStream stream, List<ImageFrameMetadat
631647
// Skip the color table for this frame if local.
632648
if (this.imageDescriptor.LocalColorTableFlag)
633649
{
634-
stream.Skip(this.imageDescriptor.LocalColorTableSize * 3);
650+
// Read and store the local color table. We allocate the maximum possible size and slice to match.
651+
int length = this.currentLocalColorTableSize = this.imageDescriptor.LocalColorTableSize * 3;
652+
this.currentLocalColorTable ??= this.configuration.MemoryAllocator.Allocate<byte>(768, AllocationOptions.Clean);
653+
stream.Read(this.currentLocalColorTable.GetSpan()[..length]);
635654
}
636655

637656
// Skip the frame indices. Pixels length + mincode size.
@@ -682,21 +701,30 @@ private void SetFrameMetadata(ImageFrameMetadata metadata)
682701
{
683702
GifFrameMetadata gifMeta = metadata.GetGifMetadata();
684703
gifMeta.ColorTableMode = GifColorTableMode.Global;
685-
gifMeta.ColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize;
686704
}
687705

688706
if (this.imageDescriptor.LocalColorTableFlag
689707
&& this.imageDescriptor.LocalColorTableSize > 0)
690708
{
691709
GifFrameMetadata gifMeta = metadata.GetGifMetadata();
692710
gifMeta.ColorTableMode = GifColorTableMode.Local;
693-
gifMeta.ColorTableLength = this.imageDescriptor.LocalColorTableSize;
711+
712+
Color[] colorTable = new Color[this.imageDescriptor.LocalColorTableSize];
713+
ReadOnlySpan<Rgb24> rgbTable = MemoryMarshal.Cast<byte, Rgb24>(this.currentLocalColorTable!.GetSpan()[..this.currentLocalColorTableSize]);
714+
for (int i = 0; i < colorTable.Length; i++)
715+
{
716+
colorTable[i] = new Color(rgbTable[i]);
717+
}
718+
719+
gifMeta.LocalColorTable = colorTable;
694720
}
695721

696722
// Graphics control extensions is optional.
697723
if (this.graphicsControlExtension != default)
698724
{
699725
GifFrameMetadata gifMeta = metadata.GetGifMetadata();
726+
gifMeta.HasTransparency = this.graphicsControlExtension.TransparencyFlag;
727+
gifMeta.TransparencyIndex = this.graphicsControlExtension.TransparencyIndex;
700728
gifMeta.FrameDelay = this.graphicsControlExtension.DelayTime;
701729
gifMeta.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
702730
}
@@ -751,14 +779,22 @@ private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream s
751779
if (this.logicalScreenDescriptor.GlobalColorTableFlag)
752780
{
753781
int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3;
754-
this.gifMetadata.GlobalColorTableLength = globalColorTableLength;
755-
756782
if (globalColorTableLength > 0)
757783
{
758784
this.globalColorTable = this.memoryAllocator.Allocate<byte>(globalColorTableLength, AllocationOptions.Clean);
759785

760-
// Read the global color table data from the stream
761-
stream.Read(this.globalColorTable.GetSpan());
786+
// Read the global color table data from the stream and preserve it in the gif metadata
787+
Span<byte> globalColorTableSpan = this.globalColorTable.GetSpan();
788+
stream.Read(globalColorTableSpan);
789+
790+
Color[] colorTable = new Color[this.logicalScreenDescriptor.GlobalColorTableSize];
791+
ReadOnlySpan<Rgb24> rgbTable = MemoryMarshal.Cast<byte, Rgb24>(globalColorTableSpan);
792+
for (int i = 0; i < colorTable.Length; i++)
793+
{
794+
colorTable[i] = new Color(rgbTable[i]);
795+
}
796+
797+
this.gifMetadata.GlobalColorTable = colorTable;
762798
}
763799
}
764800
}

0 commit comments

Comments
 (0)