diff --git a/README.md b/README.md
index 6cc8e53047..ab16bbb76a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
+
@@ -26,9 +26,16 @@ Built against [.NET Standard 1.3](https://docs.microsoft.com/en-us/dotnet/standa
## License
- ImageSharp is licensed under the [Apache License, Version 2.0](https://opensource.org/licenses/Apache-2.0)
-- An alternative Commercial License can be purchased for projects and applications requiring support.
+- An alternative Commercial Support License can be purchased **for projects and applications requiring support**.
Please visit https://sixlabors.com/pricing for details.
+## Support Six Labors
+
+Support the efforts of the development of the Six Labors projects.
+ - [Purchase a Commercial Support License :heart:](https://sixlabors.com/pricing/)
+ - [Become a sponsor via GitHub Sponsors :heart:]( https://github.com/sponsors/SixLabors)
+ - [Become a sponsor via Open Collective :heart:](https://opencollective.com/sixlabors)
+
## Documentation
- [Detailed documentation](https://sixlabors.github.io/docs/) for the ImageSharp API is available. This includes additional conceptual documentation to help you get started.
@@ -57,7 +64,7 @@ If you prefer, you can compile ImageSharp yourself (please do and help!)
- Using [Visual Studio 2019](https://visualstudio.microsoft.com/vs/)
- Make sure you have the latest version installed
- - Make sure you have [the .NET Core 3.1 SDK](https://www.microsoft.com/net/core#windows) installed
+ - Make sure you have [the .NET 5 SDK](https://www.microsoft.com/net/core#windows) installed
Alternatively, you can work from command line and/or with a lightweight editor on **both Linux/Unix and Windows**:
@@ -96,40 +103,6 @@ Please... Spread the word, contribute algorithms, submit performance improvement
- [Scott Williams](https://github.com/tocsoft)
- [Brian Popow](https://github.com/brianpopow)
-## Sponsor Six Labors
-
-Support the efforts of the development of the Six Labors projects. [[Become a sponsor :heart:](https://opencollective.com/sixlabors#sponsor)]
-
-### Platinum Sponsors
-Become a platinum sponsor with a monthly donation of $2000 (providing 32 hours of maintenance and development) and get 2 hours of dedicated support (remote support available through chat or screen-sharing) per month.
-
-In addition you get your logo (large) on our README on GitHub and the home page (large) of sixlabors.com
-
-
-
-### Gold Sponsors
-Become a gold sponsor with a monthly donation of $1000 (providing 16 hours of maintenance and development) and get 1 hour of dedicated support (remote support available through chat or screen-sharing) per month.
-
-In addition you get your logo (large) on our README on GitHub and the home page (medium) of sixlabors.com
-
-









-
-### Silver Sponsors
-Become a silver sponsor with a monthly donation of $500 (providing 8 hours of maintenance and development) and get your logo (medium) on our README on GitHub and the product pages of sixlabors.com
-









-### Bronze Sponsors
-Become a bronze sponsor with a monthly donation of $100 and get your logo (small) on our README on GitHub.
-
-
-
-
-
-
-
-
-
-
-
diff --git a/shared-infrastructure b/shared-infrastructure
index 41fff7bf7d..48e73f455f 160000
--- a/shared-infrastructure
+++ b/shared-infrastructure
@@ -1 +1 @@
-Subproject commit 41fff7bf7ddb1d118898db1ddba43b95ba6ed0bb
+Subproject commit 48e73f455f15eafefbe3175efc7433e5f277e506
diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs
index b61e63a53d..622af20478 100644
--- a/src/ImageSharp/Advanced/AotCompilerTools.cs
+++ b/src/ImageSharp/Advanced/AotCompilerTools.cs
@@ -13,6 +13,7 @@ using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
+using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@@ -57,7 +58,7 @@ namespace SixLabors.ImageSharp.Advanced
/// necessary methods to complete the SaveAsGif call. That's it, otherwise you should NEVER need this method!!!
///
[Preserve]
- private static void SeedEverything()
+ private static void SeedPixelFormats()
{
try
{
@@ -201,6 +202,7 @@ namespace SixLabors.ImageSharp.Advanced
default(JpegEncoderCore).Encode(default, default, default);
default(PngEncoderCore).Encode(default, default, default);
default(TgaEncoderCore).Encode(default, default, default);
+ default(TiffEncoderCore).Encode(default, default, default);
}
///
@@ -217,6 +219,7 @@ namespace SixLabors.ImageSharp.Advanced
default(JpegDecoderCore).Decode(default, default, default);
default(PngDecoderCore).Decode(default, default, default);
default(TgaDecoderCore).Decode(default, default, default);
+ default(TiffDecoderCore).Decode(default, default, default);
}
///
@@ -233,6 +236,7 @@ namespace SixLabors.ImageSharp.Advanced
AotCompileImageEncoder();
AotCompileImageEncoder();
AotCompileImageEncoder();
+ AotCompileImageEncoder();
}
///
@@ -249,6 +253,7 @@ namespace SixLabors.ImageSharp.Advanced
AotCompileImageDecoder();
AotCompileImageDecoder();
AotCompileImageDecoder();
+ AotCompileImageDecoder();
}
///
diff --git a/src/ImageSharp/Common/ByteOrder.cs b/src/ImageSharp/Common/ByteOrder.cs
new file mode 100644
index 0000000000..cc38f1cdee
--- /dev/null
+++ b/src/ImageSharp/Common/ByteOrder.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp
+{
+ ///
+ /// The byte order of the data stream.
+ ///
+ public enum ByteOrder
+ {
+ ///
+ /// The big-endian byte order (Motorola).
+ /// Most-significant byte comes first, and ends with the least-significant byte.
+ ///
+ BigEndian,
+
+ ///
+ /// The little-endian byte order (Intel).
+ /// Least-significant byte comes first and ends with the most-significant byte.
+ ///
+ LittleEndian
+ }
+}
diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs
index 6105422372..0581993014 100644
--- a/src/ImageSharp/Common/Helpers/Numerics.cs
+++ b/src/ImageSharp/Common/Helpers/Numerics.cs
@@ -748,5 +748,82 @@ namespace SixLabors.ImageSharp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Lerp(float value1, float value2, float amount)
=> ((value2 - value1) * amount) + value1;
+
+#if SUPPORTS_RUNTIME_INTRINSICS
+
+ ///
+ /// Accumulates 8-bit integers into by
+ /// widening them to 32-bit integers and performing four additions.
+ ///
+ ///
+ /// byte(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+ /// is widened and added onto as such:
+ ///
+ /// accumulator += i32(1, 2, 3, 4);
+ /// accumulator += i32(5, 6, 7, 8);
+ /// accumulator += i32(9, 10, 11, 12);
+ /// accumulator += i32(13, 14, 15, 16);
+ ///
+ ///
+ /// The accumulator destination.
+ /// The values to accumulate.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Accumulate(ref Vector accumulator, Vector values)
+ {
+ Vector.Widen(values, out Vector shortLow, out Vector shortHigh);
+
+ Vector.Widen(shortLow, out Vector intLow, out Vector intHigh);
+ accumulator += intLow;
+ accumulator += intHigh;
+
+ Vector.Widen(shortHigh, out intLow, out intHigh);
+ accumulator += intLow;
+ accumulator += intHigh;
+ }
+
+ ///
+ /// Reduces elements of the vector into one sum.
+ ///
+ /// The accumulator to reduce.
+ /// The sum of all elements.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int ReduceSum(Vector128 accumulator)
+ {
+ if (Ssse3.IsSupported)
+ {
+ Vector128 hadd = Ssse3.HorizontalAdd(accumulator, accumulator);
+ Vector128 swapped = Sse2.Shuffle(hadd, 0x1);
+ Vector128 tmp = Sse2.Add(hadd, swapped);
+
+ // Vector128.ToScalar() isn't optimized pre-net5.0 https://github.com/dotnet/runtime/pull/37882
+ return Sse2.ConvertToInt32(tmp);
+ }
+ else
+ {
+ int sum = 0;
+ for (int i = 0; i < Vector128.Count; i++)
+ {
+ sum += accumulator.GetElement(i);
+ }
+
+ return sum;
+ }
+ }
+
+ ///
+ /// Reduces even elements of the vector into one sum.
+ ///
+ /// The accumulator to reduce.
+ /// The sum of even elements.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int EvenReduceSum(Vector256 accumulator)
+ {
+ Vector128 vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); // add upper lane to lower lane
+ vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); // add high to low
+
+ // Vector128.ToScalar() isn't optimized pre-net5.0 https://github.com/dotnet/runtime/pull/37882
+ return Sse2.ConvertToInt32(vsum);
+ }
+#endif
}
}
diff --git a/src/ImageSharp/Common/Helpers/UnitConverter.cs b/src/ImageSharp/Common/Helpers/UnitConverter.cs
index 4a6e6abcb6..efc0e0e152 100644
--- a/src/ImageSharp/Common/Helpers/UnitConverter.cs
+++ b/src/ImageSharp/Common/Helpers/UnitConverter.cs
@@ -30,6 +30,11 @@ namespace SixLabors.ImageSharp.Common.Helpers
///
private const double InchesInMeter = 1 / 0.0254D;
+ ///
+ /// The default resolution unit value.
+ ///
+ private const PixelResolutionUnit DefaultResolutionUnit = PixelResolutionUnit.PixelsPerInch;
+
///
/// Scales the value from centimeters to meters.
///
@@ -89,7 +94,50 @@ namespace SixLabors.ImageSharp.Common.Helpers
IExifValue resolution = profile.GetValue(ExifTag.ResolutionUnit);
// EXIF is 1, 2, 3 so we minus "1" off the result.
- return resolution is null ? default : (PixelResolutionUnit)(byte)(resolution.Value - 1);
+ return resolution is null ? DefaultResolutionUnit : (PixelResolutionUnit)(byte)(resolution.Value - 1);
+ }
+
+ ///
+ /// Sets the exif profile resolution values.
+ ///
+ /// The exif profile.
+ /// The resolution unit.
+ /// The horizontal resolution value.
+ /// The vertical resolution value.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static void SetResolutionValues(ExifProfile exifProfile, PixelResolutionUnit unit, double horizontal, double vertical)
+ {
+ switch (unit)
+ {
+ case PixelResolutionUnit.AspectRatio:
+ case PixelResolutionUnit.PixelsPerInch:
+ case PixelResolutionUnit.PixelsPerCentimeter:
+ break;
+ case PixelResolutionUnit.PixelsPerMeter:
+ {
+ unit = PixelResolutionUnit.PixelsPerCentimeter;
+ horizontal = UnitConverter.MeterToCm(horizontal);
+ vertical = UnitConverter.MeterToCm(vertical);
+ }
+
+ break;
+ default:
+ unit = PixelResolutionUnit.PixelsPerInch;
+ break;
+ }
+
+ exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)(unit + 1));
+
+ if (unit == PixelResolutionUnit.AspectRatio)
+ {
+ exifProfile.RemoveValue(ExifTag.XResolution);
+ exifProfile.RemoveValue(ExifTag.YResolution);
+ }
+ else
+ {
+ exifProfile.SetValue(ExifTag.XResolution, new Rational(horizontal));
+ exifProfile.SetValue(ExifTag.YResolution, new Rational(vertical));
+ }
}
}
}
diff --git a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs b/src/ImageSharp/Compression/Zlib/Adler32.cs
similarity index 99%
rename from src/ImageSharp/Formats/Png/Zlib/Adler32.cs
rename to src/ImageSharp/Compression/Zlib/Adler32.cs
index 534aba8f5a..9b3abd298b 100644
--- a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs
+++ b/src/ImageSharp/Compression/Zlib/Adler32.cs
@@ -9,7 +9,7 @@ using System.Runtime.Intrinsics.X86;
#endif
#pragma warning disable IDE0007 // Use implicit type
-namespace SixLabors.ImageSharp.Formats.Png.Zlib
+namespace SixLabors.ImageSharp.Compression.Zlib
{
///
/// Calculates the 32 bit Adler checksum of a given buffer according to
diff --git a/src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs b/src/ImageSharp/Compression/Zlib/Crc32.Lut.cs
similarity index 98%
rename from src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs
rename to src/ImageSharp/Compression/Zlib/Crc32.Lut.cs
index 5007833539..059bd9f312 100644
--- a/src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs
+++ b/src/ImageSharp/Compression/Zlib/Crc32.Lut.cs
@@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
-namespace SixLabors.ImageSharp.Formats.Png.Zlib
+namespace SixLabors.ImageSharp.Compression.Zlib
{
///
/// Contains precalulated tables for scalar calculations.
diff --git a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs b/src/ImageSharp/Compression/Zlib/Crc32.cs
similarity index 99%
rename from src/ImageSharp/Formats/Png/Zlib/Crc32.cs
rename to src/ImageSharp/Compression/Zlib/Crc32.cs
index 6b19987cb1..0ba368df64 100644
--- a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs
+++ b/src/ImageSharp/Compression/Zlib/Crc32.cs
@@ -9,7 +9,7 @@ using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
-namespace SixLabors.ImageSharp.Formats.Png.Zlib
+namespace SixLabors.ImageSharp.Compression.Zlib
{
///
/// Calculates the 32 bit Cyclic Redundancy Check (CRC) checksum of a given buffer
diff --git a/src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs b/src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs
new file mode 100644
index 0000000000..2edf76e7d5
--- /dev/null
+++ b/src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs
@@ -0,0 +1,81 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Compression.Zlib
+{
+ ///
+ /// Provides enumeration of available deflate compression levels.
+ ///
+ public enum DeflateCompressionLevel
+ {
+ ///
+ /// Level 0. Equivalent to .
+ ///
+ Level0 = 0,
+
+ ///
+ /// No compression. Equivalent to .
+ ///
+ NoCompression = Level0,
+
+ ///
+ /// Level 1. Equivalent to .
+ ///
+ Level1 = 1,
+
+ ///
+ /// Best speed compression level.
+ ///
+ BestSpeed = Level1,
+
+ ///
+ /// Level 2.
+ ///
+ Level2 = 2,
+
+ ///
+ /// Level 3.
+ ///
+ Level3 = 3,
+
+ ///
+ /// Level 4.
+ ///
+ Level4 = 4,
+
+ ///
+ /// Level 5.
+ ///
+ Level5 = 5,
+
+ ///
+ /// Level 6. Equivalent to .
+ ///
+ Level6 = 6,
+
+ ///
+ /// The default compression level. Equivalent to .
+ ///
+ DefaultCompression = Level6,
+
+ ///
+ /// Level 7.
+ ///
+ Level7 = 7,
+
+ ///
+ /// Level 8.
+ ///
+ Level8 = 8,
+
+ ///
+ /// Level 9. Equivalent to .
+ ///
+ Level9 = 9,
+
+ ///
+ /// Best compression level. Equivalent to .
+ ///
+ BestCompression = Level9,
+ }
+}
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs b/src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs
similarity index 96%
rename from src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs
rename to src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs
index a5d129c92c..02590ca253 100644
--- a/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs
+++ b/src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs
@@ -4,7 +4,7 @@
using System;
using System.Runtime.CompilerServices;
-namespace SixLabors.ImageSharp.Formats.Png.Zlib
+namespace SixLabors.ImageSharp.Compression.Zlib
{
internal static class DeflateThrowHelper
{
diff --git a/src/ImageSharp/Formats/Png/Zlib/Deflater.cs b/src/ImageSharp/Compression/Zlib/Deflater.cs
similarity index 99%
rename from src/ImageSharp/Formats/Png/Zlib/Deflater.cs
rename to src/ImageSharp/Compression/Zlib/Deflater.cs
index 8389215813..800c96703e 100644
--- a/src/ImageSharp/Formats/Png/Zlib/Deflater.cs
+++ b/src/ImageSharp/Compression/Zlib/Deflater.cs
@@ -5,7 +5,7 @@ using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
-namespace SixLabors.ImageSharp.Formats.Png.Zlib
+namespace SixLabors.ImageSharp.Compression.Zlib
{
///
/// This class compresses input with the deflate algorithm described in RFC 1951.
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs b/src/ImageSharp/Compression/Zlib/DeflaterConstants.cs
similarity index 98%
rename from src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs
rename to src/ImageSharp/Compression/Zlib/DeflaterConstants.cs
index ec224d748d..30bd75ffcd 100644
--- a/src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs
+++ b/src/ImageSharp/Compression/Zlib/DeflaterConstants.cs
@@ -4,7 +4,7 @@
//
using System;
-namespace SixLabors.ImageSharp.Formats.Png.Zlib
+namespace SixLabors.ImageSharp.Compression.Zlib
{
///
/// This class contains constants used for deflation.
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs b/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs
similarity index 99%
rename from src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs
rename to src/ImageSharp/Compression/Zlib/DeflaterEngine.cs
index 797f5d2101..d3cfa7c3d1 100644
--- a/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs
+++ b/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs
@@ -6,7 +6,7 @@ using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
-namespace SixLabors.ImageSharp.Formats.Png.Zlib
+namespace SixLabors.ImageSharp.Compression.Zlib
{
///
/// Strategies for deflater
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs
similarity index 99%
rename from src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs
rename to src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs
index 96b47fb24b..d6892dfd2d 100644
--- a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs
+++ b/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs
@@ -7,7 +7,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
-namespace SixLabors.ImageSharp.Formats.Png.Zlib
+namespace SixLabors.ImageSharp.Compression.Zlib
{
///
/// Performs Deflate Huffman encoding.
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs b/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs
similarity index 98%
rename from src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs
rename to src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs
index 5c5651996f..cbbf7ea792 100644
--- a/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs
+++ b/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs
@@ -5,7 +5,7 @@ using System;
using System.IO;
using SixLabors.ImageSharp.Memory;
-namespace SixLabors.ImageSharp.Formats.Png.Zlib
+namespace SixLabors.ImageSharp.Compression.Zlib
{
///
/// A special stream deflating or compressing the bytes that are
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs b/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs
similarity index 99%
rename from src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs
rename to src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs
index f702a7eada..36dfd92da2 100644
--- a/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs
+++ b/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs
@@ -6,7 +6,7 @@ using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
-namespace SixLabors.ImageSharp.Formats.Png.Zlib
+namespace SixLabors.ImageSharp.Compression.Zlib
{
///
/// Stores pending data for writing data to the Deflater.
diff --git a/src/ImageSharp/Formats/Png/Zlib/README.md b/src/ImageSharp/Compression/Zlib/README.md
similarity index 100%
rename from src/ImageSharp/Formats/Png/Zlib/README.md
rename to src/ImageSharp/Compression/Zlib/README.md
diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs
similarity index 89%
rename from src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs
rename to src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs
index 06c6e3dea4..44883665ab 100644
--- a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs
+++ b/src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs
@@ -4,9 +4,10 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Memory;
-namespace SixLabors.ImageSharp.Formats.Png.Zlib
+namespace SixLabors.ImageSharp.Compression.Zlib
{
///
/// Provides methods and properties for compressing streams by using the Zlib Deflate algorithm.
@@ -39,9 +40,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
///
/// The stream responsible for compressing the input stream.
///
- // private DeflateStream deflateStream;
private DeflaterOutputStream deflateStream;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The memory allocator to use for buffer allocations.
+ /// The stream to compress.
+ /// The compression level.
+ public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, DeflateCompressionLevel level)
+ : this(memoryAllocator, stream, (PngCompressionLevel)level)
+ {
+ }
+
///
/// Initializes a new instance of the class.
///
diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs
similarity index 99%
rename from src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs
rename to src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs
index 52ef0e85ba..f4b0543b84 100644
--- a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs
+++ b/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs
@@ -6,7 +6,7 @@ using System.IO;
using System.IO.Compression;
using SixLabors.ImageSharp.IO;
-namespace SixLabors.ImageSharp.Formats.Png.Zlib
+namespace SixLabors.ImageSharp.Compression.Zlib
{
///
/// Provides methods and properties for deframing streams from PNGs.
diff --git a/src/ImageSharp/Formats/Png/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf b/src/ImageSharp/Compression/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf
similarity index 100%
rename from src/ImageSharp/Formats/Png/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf
rename to src/ImageSharp/Compression/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf
diff --git a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
index 6fdf8d6342..7801e48a91 100644
--- a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Bmp
@@ -8,6 +8,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
public enum BmpBitsPerPixel : short
{
+ ///
+ /// 1 bit per pixel.
+ ///
+ Pixel1 = 1,
+
+ ///
+ /// 4 bits per pixel.
+ ///
+ Pixel4 = 4,
+
///
/// 8 bits per pixel. Each pixel consists of 1 byte.
///
@@ -28,4 +38,4 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
Pixel32 = 32
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 0be0385725..f6fefda485 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -1303,15 +1303,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
short bitsPerPixel = this.infoHeader.BitsPerPixel;
this.bmpMetadata = this.metadata.GetBmpMetadata();
this.bmpMetadata.InfoHeaderType = infoHeaderType;
-
- // We can only encode at these bit rates so far (1 bit and 4 bit are still missing).
- if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel8)
- || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel16)
- || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24)
- || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel32))
- {
- this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
- }
+ this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
}
///
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
index 2f5c4b7cf7..f256ed9f81 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
@@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// Gets or sets the quantizer for reducing the color count for 8-Bit images.
- /// Defaults to OctreeQuantizer.
+ /// Defaults to Wu Quantizer.
///
public IQuantizer Quantizer { get; set; }
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
index 7819b1ebdb..5cf54388d3 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
@@ -51,6 +51,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
private const int ColorPaletteSize8Bit = 1024;
+ ///
+ /// The color palette for an 4 bit image will have 16 entry's with 4 bytes for each entry.
+ ///
+ private const int ColorPaletteSize4Bit = 64;
+
+ ///
+ /// The color palette for an 1 bit image will have 2 entry's with 4 bytes for each entry.
+ ///
+ private const int ColorPaletteSize1Bit = 8;
+
///
/// Used for allocating memory during processing operations.
///
@@ -74,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private readonly bool writeV4Header;
///
- /// The quantizer for reducing the color count for 8-Bit images.
+ /// The quantizer for reducing the color count for 8-Bit, 4-Bit and 1-Bit images.
///
private readonly IQuantizer quantizer;
@@ -88,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.memoryAllocator = memoryAllocator;
this.bitsPerPixel = options.BitsPerPixel;
this.writeV4Header = options.SupportTransparency;
- this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
+ this.quantizer = options.Quantizer ?? KnownQuantizers.Wu;
}
///
@@ -107,7 +117,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.configuration = image.GetConfiguration();
ImageMetadata metadata = image.Metadata;
BmpMetadata bmpMetadata = metadata.GetBmpMetadata();
- this.bitsPerPixel = this.bitsPerPixel ?? bmpMetadata.BitsPerPixel;
+ this.bitsPerPixel ??= bmpMetadata.BitsPerPixel;
short bpp = (short)this.bitsPerPixel;
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
@@ -166,7 +176,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
infoHeader.Compression = BmpCompression.BitFields;
}
- int colorPaletteSize = this.bitsPerPixel == BmpBitsPerPixel.Pixel8 ? ColorPaletteSize8Bit : 0;
+ int colorPaletteSize = 0;
+ if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8)
+ {
+ colorPaletteSize = ColorPaletteSize8Bit;
+ }
+ else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel4)
+ {
+ colorPaletteSize = ColorPaletteSize4Bit;
+ }
+ else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel1)
+ {
+ colorPaletteSize = ColorPaletteSize1Bit;
+ }
var fileHeader = new BmpFileHeader(
type: BmpConstants.TypeMarkers.Bitmap,
@@ -224,6 +246,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
case BmpBitsPerPixel.Pixel8:
this.Write8Bit(stream, image);
break;
+
+ case BmpBitsPerPixel.Pixel4:
+ this.Write4BitColor(stream, image);
+ break;
+
+ case BmpBitsPerPixel.Pixel1:
+ this.Write1BitColor(stream, image);
+ break;
}
}
@@ -308,7 +338,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
///
- /// Writes an 8 Bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
+ /// Writes an 8 bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
///
/// The type of the pixel.
/// The to write to.
@@ -332,7 +362,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
///
- /// Writes an 8 Bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
+ /// Writes an 8 bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
///
/// The type of the pixel.
/// The to write to.
@@ -344,16 +374,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration);
using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
- ReadOnlySpan quantizedColors = quantized.Palette.Span;
- var quantizedColorBytes = quantizedColors.Length * 4;
- PixelOperations.Instance.ToBgra32(this.configuration, quantizedColors, MemoryMarshal.Cast(colorPalette.Slice(0, quantizedColorBytes)));
- Span colorPaletteAsUInt = MemoryMarshal.Cast(colorPalette);
- for (int i = 0; i < colorPaletteAsUInt.Length; i++)
- {
- colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0.
- }
-
- stream.Write(colorPalette);
+ ReadOnlySpan quantizedColorPalette = quantized.Palette.Span;
+ this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
for (int y = image.Height - 1; y >= 0; y--)
{
@@ -368,7 +390,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
///
- /// Writes an 8 Bit gray image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
+ /// Writes an 8 bit gray image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
///
/// The type of the pixel.
/// The to write to.
@@ -404,5 +426,136 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
}
+
+ ///
+ /// Writes an 4 bit color image with a color palette. The color palette has 16 entry's with 4 bytes for each entry.
+ ///
+ /// The type of the pixel.
+ /// The to write to.
+ /// The containing pixel data.
+ private void Write4BitColor(Stream stream, ImageFrame image)
+ where TPixel : unmanaged, IPixel
+ {
+ using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions()
+ {
+ MaxColors = 16
+ });
+ using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
+ using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize4Bit, AllocationOptions.Clean);
+
+ Span colorPalette = colorPaletteBuffer.GetSpan();
+ ReadOnlySpan quantizedColorPalette = quantized.Palette.Span;
+ this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
+
+ ReadOnlySpan pixelRowSpan = quantized.GetPixelRowSpan(0);
+ int rowPadding = pixelRowSpan.Length % 2 != 0 ? this.padding - 1 : this.padding;
+ for (int y = image.Height - 1; y >= 0; y--)
+ {
+ pixelRowSpan = quantized.GetPixelRowSpan(y);
+
+ int endIdx = pixelRowSpan.Length % 2 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 1;
+ for (int i = 0; i < endIdx; i += 2)
+ {
+ stream.WriteByte((byte)((pixelRowSpan[i] << 4) | pixelRowSpan[i + 1]));
+ }
+
+ if (pixelRowSpan.Length % 2 != 0)
+ {
+ stream.WriteByte((byte)((pixelRowSpan[pixelRowSpan.Length - 1] << 4) | 0));
+ }
+
+ for (int i = 0; i < rowPadding; i++)
+ {
+ stream.WriteByte(0);
+ }
+ }
+ }
+
+ ///
+ /// Writes a 1 bit image with a color palette. The color palette has 2 entry's with 4 bytes for each entry.
+ ///
+ /// The type of the pixel.
+ /// The to write to.
+ /// The containing pixel data.
+ private void Write1BitColor(Stream stream, ImageFrame image)
+ where TPixel : unmanaged, IPixel
+ {
+ using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions()
+ {
+ MaxColors = 2
+ });
+ using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
+ using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize1Bit, AllocationOptions.Clean);
+
+ Span colorPalette = colorPaletteBuffer.GetSpan();
+ ReadOnlySpan quantizedColorPalette = quantized.Palette.Span;
+ this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
+
+ ReadOnlySpan quantizedPixelRow = quantized.GetPixelRowSpan(0);
+ int rowPadding = quantizedPixelRow.Length % 8 != 0 ? this.padding - 1 : this.padding;
+ for (int y = image.Height - 1; y >= 0; y--)
+ {
+ quantizedPixelRow = quantized.GetPixelRowSpan(y);
+
+ int endIdx = quantizedPixelRow.Length % 8 == 0 ? quantizedPixelRow.Length : quantizedPixelRow.Length - 8;
+ for (int i = 0; i < endIdx; i += 8)
+ {
+ Write1BitPalette(stream, i, i + 8, quantizedPixelRow);
+ }
+
+ if (quantizedPixelRow.Length % 8 != 0)
+ {
+ int startIdx = quantizedPixelRow.Length - 7;
+ endIdx = quantizedPixelRow.Length;
+ Write1BitPalette(stream, startIdx, endIdx, quantizedPixelRow);
+ }
+
+ for (int i = 0; i < rowPadding; i++)
+ {
+ stream.WriteByte(0);
+ }
+ }
+ }
+
+ ///
+ /// Writes the color palette to the stream. The color palette has 4 bytes for each entry.
+ ///
+ /// The type of the pixel.
+ /// The to write to.
+ /// The color palette from the quantized image.
+ /// A temporary byte span to write the color palette to.
+ private void WriteColorPalette(Stream stream, ReadOnlySpan quantizedColorPalette, Span colorPalette)
+ where TPixel : unmanaged, IPixel
+ {
+ int quantizedColorBytes = quantizedColorPalette.Length * 4;
+ PixelOperations.Instance.ToBgra32(this.configuration, quantizedColorPalette, MemoryMarshal.Cast(colorPalette.Slice(0, quantizedColorBytes)));
+ Span colorPaletteAsUInt = MemoryMarshal.Cast(colorPalette);
+ for (int i = 0; i < colorPaletteAsUInt.Length; i++)
+ {
+ colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0.
+ }
+
+ stream.Write(colorPalette);
+ }
+
+ ///
+ /// Writes a 1-bit palette.
+ ///
+ /// The stream to write the palette to.
+ /// The start index.
+ /// The end index.
+ /// A quantized pixel row.
+ private static void Write1BitPalette(Stream stream, int startIdx, int endIdx, ReadOnlySpan quantizedPixelRow)
+ {
+ int shift = 7;
+ byte indices = 0;
+ for (int j = startIdx; j < endIdx; j++)
+ {
+ indices = (byte)(indices | ((byte)(quantizedPixelRow[j] & 1) << shift));
+ shift--;
+ }
+
+ stream.WriteByte(indices);
+ }
}
}
diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
index d4a22d66ea..30aa70452e 100644
--- a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@@ -24,8 +24,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
bool SupportTransparency { get; }
///
- /// Gets the quantizer for reducing the color count for 8-Bit images.
+ /// Gets the quantizer for reducing the color count for 8-Bit, 4-Bit, and 1-Bit images.
///
IQuantizer Quantizer { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs
index 94f0585245..1110d3b2f1 100644
--- a/src/ImageSharp/Formats/ImageExtensions.Save.cs
+++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs
@@ -6,14 +6,14 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
-// using SixLabors.ImageSharp.Formats.Experimental.Tiff;
-using SixLabors.ImageSharp.Formats.Experimental.Webp;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
+using SixLabors.ImageSharp.Formats.Experimental.Webp;
+using SixLabors.ImageSharp.Formats.Tiff;
namespace SixLabors.ImageSharp
{
@@ -538,7 +538,7 @@ namespace SixLabors.ImageSharp
cancellationToken);
///
- /// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Webp format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -546,7 +546,7 @@ namespace SixLabors.ImageSharp
public static void SaveAsWebp(this Image source, string path) => SaveAsWebp(source, path, null);
///
- /// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Webp format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -555,7 +555,7 @@ namespace SixLabors.ImageSharp
public static Task SaveAsWebpAsync(this Image source, string path) => SaveAsWebpAsync(source, path, null);
///
- /// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Webp format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -566,7 +566,7 @@ namespace SixLabors.ImageSharp
=> SaveAsWebpAsync(source, path, null, cancellationToken);
///
- /// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Webp format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -578,7 +578,7 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance));
///
- /// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Webp format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -593,7 +593,7 @@ namespace SixLabors.ImageSharp
cancellationToken);
///
- /// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Webp format.
///
/// The image this method extends.
/// The stream to save the image to.
@@ -602,7 +602,7 @@ namespace SixLabors.ImageSharp
=> SaveAsWebp(source, stream, null);
///
- /// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Webp format.
///
/// The image this method extends.
/// The stream to save the image to.
@@ -613,7 +613,7 @@ namespace SixLabors.ImageSharp
=> SaveAsWebpAsync(source, stream, null, cancellationToken);
///
- /// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Webp format.
///
/// The image this method extends.
/// The stream to save the image to.
@@ -626,7 +626,7 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance));
///
- /// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Webp format.
///
/// The image this method extends.
/// The stream to save the image to.
@@ -640,5 +640,108 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance),
cancellationToken);
+ ///
+ /// Saves the image to the given stream with the Tiff format.
+ ///
+ /// The image this method extends.
+ /// The file path to save the image to.
+ /// Thrown if the path is null.
+ public static void SaveAsTiff(this Image source, string path) => SaveAsTiff(source, path, null);
+
+ ///
+ /// Saves the image to the given stream with the Tiff format.
+ ///
+ /// The image this method extends.
+ /// The file path to save the image to.
+ /// Thrown if the path is null.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsTiffAsync(this Image source, string path) => SaveAsTiffAsync(source, path, null);
+
+ ///
+ /// Saves the image to the given stream with the Tiff format.
+ ///
+ /// The image this method extends.
+ /// The file path to save the image to.
+ /// The token to monitor for cancellation requests.
+ /// Thrown if the path is null.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsTiffAsync(this Image source, string path, CancellationToken cancellationToken)
+ => SaveAsTiffAsync(source, path, null, cancellationToken);
+
+ ///
+ /// Saves the image to the given stream with the Tiff format.
+ ///
+ /// The image this method extends.
+ /// The file path to save the image to.
+ /// The encoder to save the image with.
+ /// Thrown if the path is null.
+ public static void SaveAsTiff(this Image source, string path, TiffEncoder encoder) =>
+ source.Save(
+ path,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance));
+
+ ///
+ /// Saves the image to the given stream with the Tiff format.
+ ///
+ /// The image this method extends.
+ /// The file path to save the image to.
+ /// The encoder to save the image with.
+ /// The token to monitor for cancellation requests.
+ /// Thrown if the path is null.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsTiffAsync(this Image source, string path, TiffEncoder encoder, CancellationToken cancellationToken = default) =>
+ source.SaveAsync(
+ path,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance),
+ cancellationToken);
+
+ ///
+ /// Saves the image to the given stream with the Tiff format.
+ ///
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// Thrown if the stream is null.
+ public static void SaveAsTiff(this Image source, Stream stream)
+ => SaveAsTiff(source, stream, null);
+
+ ///
+ /// Saves the image to the given stream with the Tiff format.
+ ///
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// The token to monitor for cancellation requests.
+ /// Thrown if the stream is null.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsTiffAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
+ => SaveAsTiffAsync(source, stream, null, cancellationToken);
+
+ ///
+ /// Saves the image to the given stream with the Tiff format.
+ ///
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// The encoder to save the image with.
+ /// Thrown if the stream is null.
+ /// A representing the asynchronous operation.
+ public static void SaveAsTiff(this Image source, Stream stream, TiffEncoder encoder)
+ => source.Save(
+ stream,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance));
+
+ ///
+ /// Saves the image to the given stream with the Tiff format.
+ ///
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// The encoder to save the image with.
+ /// The token to monitor for cancellation requests.
+ /// Thrown if the stream is null.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsTiffAsync(this Image source, Stream stream, TiffEncoder encoder, CancellationToken cancellationToken = default) =>
+ source.SaveAsync(
+ stream,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance),
+ cancellationToken);
+
}
}
diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.tt b/src/ImageSharp/Formats/ImageExtensions.Save.tt
index a9102cb292..874f3ab0dc 100644
--- a/src/ImageSharp/Formats/ImageExtensions.Save.tt
+++ b/src/ImageSharp/Formats/ImageExtensions.Save.tt
@@ -9,8 +9,6 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
-// using SixLabors.ImageSharp.Formats.Experimental.Tiff;
-using SixLabors.ImageSharp.Formats.Experimental.Webp;
<#
var formats = new []{
@@ -19,15 +17,12 @@ using SixLabors.ImageSharp.Formats.Experimental.Webp;
"Jpeg",
"Png",
"Tga",
- "Webp"
+ "Webp",
+ "Tiff",
};
foreach (string fmt in formats)
{
- if (fmt == "Tiff" || fmt == "Webp")
- {
- continue;
- }
#>
using SixLabors.ImageSharp.Formats.<#= fmt #>;
<#
@@ -45,10 +40,9 @@ namespace SixLabors.ImageSharp
<#
foreach (string fmt in formats)
{
- string experimentalString = fmt == "Tiff" || fmt == "Webp" ? @"EXPERIMENTAL! " : "";
#>
///
- /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
+ /// Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -56,7 +50,7 @@ namespace SixLabors.ImageSharp
public static void SaveAs<#= fmt #>(this Image source, string path) => SaveAs<#= fmt #>(source, path, null);
///
- /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
+ /// Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -65,7 +59,7 @@ namespace SixLabors.ImageSharp
public static Task SaveAs<#= fmt #>Async(this Image source, string path) => SaveAs<#= fmt #>Async(source, path, null);
///
- /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
+ /// Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -76,7 +70,7 @@ namespace SixLabors.ImageSharp
=> SaveAs<#= fmt #>Async(source, path, null, cancellationToken);
///
- /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
+ /// Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -88,7 +82,7 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance));
///
- /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
+ /// Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -103,7 +97,7 @@ namespace SixLabors.ImageSharp
cancellationToken);
///
- /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
+ /// Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The stream to save the image to.
@@ -112,7 +106,7 @@ namespace SixLabors.ImageSharp
=> SaveAs<#= fmt #>(source, stream, null);
///
- /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
+ /// Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The stream to save the image to.
@@ -123,7 +117,7 @@ namespace SixLabors.ImageSharp
=> SaveAs<#= fmt #>Async(source, stream, null, cancellationToken);
///
- /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
+ /// Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The stream to save the image to.
@@ -136,7 +130,7 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance));
///
- /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
+ /// Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The stream to save the image to.
diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
index d1c214e3d6..0ab1413974 100644
--- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
@@ -5,6 +5,11 @@ using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+#if SUPPORTS_RUNTIME_INTRINSICS
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
+
namespace SixLabors.ImageSharp.Formats.Png.Filters
{
///
@@ -79,6 +84,79 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
sum += Numerics.Abs(unchecked((sbyte)res));
}
+#if SUPPORTS_RUNTIME_INTRINSICS
+ if (Avx2.IsSupported)
+ {
+ Vector256 zero = Vector256.Zero;
+ Vector256 sumAccumulator = Vector256.Zero;
+ Vector256 allBitsSet = Avx2.CompareEqual(sumAccumulator, sumAccumulator).AsByte();
+
+ for (int xLeft = x - bytesPerPixel; x + Vector256.Count <= scanline.Length; xLeft += Vector256.Count)
+ {
+ Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x));
+ Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft));
+ Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x));
+
+ Vector256 avg = Avx2.Xor(Avx2.Average(Avx2.Xor(left, allBitsSet), Avx2.Xor(above, allBitsSet)), allBitsSet);
+ Vector256 res = Avx2.Subtract(scan, avg);
+
+ Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
+ x += Vector256.Count;
+
+ sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32());
+ }
+
+ sum += Numerics.EvenReduceSum(sumAccumulator);
+ }
+ else if (Sse2.IsSupported)
+ {
+ Vector128 zero8 = Vector128.Zero;
+ Vector128 zero16 = Vector128.Zero;
+ Vector128 sumAccumulator = Vector128.Zero;
+ Vector128 allBitsSet = Sse2.CompareEqual(sumAccumulator, sumAccumulator).AsByte();
+
+ for (int xLeft = x - bytesPerPixel; x + Vector128.Count <= scanline.Length; xLeft += Vector128.Count)
+ {
+ Vector128 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x));
+ Vector128 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft));
+ Vector128 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x));
+
+ Vector128 avg = Sse2.Xor(Sse2.Average(Sse2.Xor(left, allBitsSet), Sse2.Xor(above, allBitsSet)), allBitsSet);
+ Vector128 res = Sse2.Subtract(scan, avg);
+
+ Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
+ x += Vector128.Count;
+
+ Vector128 absRes;
+ if (Ssse3.IsSupported)
+ {
+ absRes = Ssse3.Abs(res.AsSByte()).AsSByte();
+ }
+ else
+ {
+ Vector128 mask = Sse2.CompareGreaterThan(res.AsSByte(), zero8);
+ mask = Sse2.Xor(mask, allBitsSet.AsSByte());
+ absRes = Sse2.Xor(Sse2.Add(res.AsSByte(), mask), mask);
+ }
+
+ Vector128 loRes16 = Sse2.UnpackLow(absRes, zero8).AsInt16();
+ Vector128 hiRes16 = Sse2.UnpackHigh(absRes, zero8).AsInt16();
+
+ Vector128 loRes32 = Sse2.UnpackLow(loRes16, zero16).AsInt32();
+ Vector128 hiRes32 = Sse2.UnpackHigh(loRes16, zero16).AsInt32();
+ sumAccumulator = Sse2.Add(sumAccumulator, loRes32);
+ sumAccumulator = Sse2.Add(sumAccumulator, hiRes32);
+
+ loRes32 = Sse2.UnpackLow(hiRes16, zero16).AsInt32();
+ hiRes32 = Sse2.UnpackHigh(hiRes16, zero16).AsInt32();
+ sumAccumulator = Sse2.Add(sumAccumulator, loRes32);
+ sumAccumulator = Sse2.Add(sumAccumulator, hiRes32);
+ }
+
+ sum += Numerics.ReduceSum(sumAccumulator);
+ }
+#endif
+
for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
index fab6788061..e8e0aa7043 100644
--- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
@@ -2,9 +2,15 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+#if SUPPORTS_RUNTIME_INTRINSICS
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
+
namespace SixLabors.ImageSharp.Formats.Png.Filters
{
///
@@ -82,6 +88,53 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
sum += Numerics.Abs(unchecked((sbyte)res));
}
+#if SUPPORTS_RUNTIME_INTRINSICS
+ if (Avx2.IsSupported)
+ {
+ Vector256 zero = Vector256.Zero;
+ Vector256 sumAccumulator = Vector256.Zero;
+
+ for (int xLeft = x - bytesPerPixel; x + Vector256.Count <= scanline.Length; xLeft += Vector256.Count)
+ {
+ Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x));
+ Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft));
+ Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x));
+ Vector256 upperLeft = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, xLeft));
+
+ Vector256 res = Avx2.Subtract(scan, PaethPredictor(left, above, upperLeft));
+ Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
+ x += Vector256.Count;
+
+ sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32());
+ }
+
+ sum += Numerics.EvenReduceSum(sumAccumulator);
+ }
+ else if (Vector.IsHardwareAccelerated)
+ {
+ Vector sumAccumulator = Vector.Zero;
+
+ for (int xLeft = x - bytesPerPixel; x + Vector.Count <= scanline.Length; xLeft += Vector.Count)
+ {
+ Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x));
+ Vector left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft));
+ Vector above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x));
+ Vector upperLeft = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, xLeft));
+
+ Vector res = scan - PaethPredictor(left, above, upperLeft);
+ Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
+ x += Vector.Count;
+
+ Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res))));
+ }
+
+ for (int i = 0; i < Vector.Count; i++)
+ {
+ sum += (int)sumAccumulator[i];
+ }
+ }
+#endif
+
for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
@@ -127,5 +180,70 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
return upperLeft;
}
+
+#if SUPPORTS_RUNTIME_INTRINSICS
+ private static Vector256 PaethPredictor(Vector256 left, Vector256 above, Vector256 upleft)
+ {
+ Vector256 zero = Vector256.Zero;
+
+ // Here, we refactor pa = abs(p - left) = abs(left + above - upleft - left)
+ // to pa = abs(above - upleft). Same deal for pb.
+ // Using saturated subtraction, if the result is negative, the output is zero.
+ // If we subtract in both directions and `or` the results, only one can be
+ // non-zero, so we end up with the absolute value.
+ Vector256 sac = Avx2.SubtractSaturate(above, upleft);
+ Vector256 sbc = Avx2.SubtractSaturate(left, upleft);
+ Vector256 pa = Avx2.Or(Avx2.SubtractSaturate(upleft, above), sac);
+ Vector256 pb = Avx2.Or(Avx2.SubtractSaturate(upleft, left), sbc);
+
+ // pc = abs(left + above - upleft - upleft), or abs(left - upleft + above - upleft).
+ // We've already calculated left - upleft and above - upleft in `sac` and `sbc`.
+ // If they are both negative or both positive, the absolute value of their
+ // sum can't possibly be less than `pa` or `pb`, so we'll never use the value.
+ // We make a mask that sets the value to 255 if they either both got
+ // saturated to zero or both didn't. Then we calculate the absolute value
+ // of their difference using saturated subtract and `or`, same as before,
+ // keeping the value only where the mask isn't set.
+ Vector256 pm = Avx2.CompareEqual(Avx2.CompareEqual(sac, zero), Avx2.CompareEqual(sbc, zero));
+ Vector256 pc = Avx2.Or(pm, Avx2.Or(Avx2.SubtractSaturate(pb, pa), Avx2.SubtractSaturate(pa, pb)));
+
+ // Finally, blend the values together. We start with `upleft` and overwrite on
+ // tied values so that the `left`, `above`, `upleft` precedence is preserved.
+ Vector256 minbc = Avx2.Min(pc, pb);
+ Vector256 resbc = Avx2.BlendVariable(upleft, above, Avx2.CompareEqual(minbc, pb));
+ return Avx2.BlendVariable(resbc, left, Avx2.CompareEqual(Avx2.Min(minbc, pa), pa));
+ }
+
+ private static Vector PaethPredictor(Vector left, Vector above, Vector upperLeft)
+ {
+ Vector.Widen(left, out Vector a1, out Vector a2);
+ Vector.Widen(above, out Vector b1, out Vector b2);
+ Vector.Widen(upperLeft, out Vector c1, out Vector c2);
+
+ Vector p1 = PaethPredictor(Vector.AsVectorInt16(a1), Vector.AsVectorInt16(b1), Vector.AsVectorInt16(c1));
+ Vector p2 = PaethPredictor(Vector.AsVectorInt16(a2), Vector.AsVectorInt16(b2), Vector.AsVectorInt16(c2));
+ return Vector.AsVectorByte(Vector.Narrow(p1, p2));
+ }
+
+ private static Vector PaethPredictor(Vector left, Vector above, Vector upperLeft)
+ {
+ Vector p = left + above - upperLeft;
+ var pa = Vector.Abs(p - left);
+ var pb = Vector.Abs(p - above);
+ var pc = Vector.Abs(p - upperLeft);
+
+ var pa_pb = Vector.LessThanOrEqual(pa, pb);
+ var pa_pc = Vector.LessThanOrEqual(pa, pc);
+ var pb_pc = Vector.LessThanOrEqual(pb, pc);
+
+ return Vector.ConditionalSelect(
+ condition: Vector.BitwiseAnd(pa_pb, pa_pc),
+ left: left,
+ right: Vector.ConditionalSelect(
+ condition: pb_pc,
+ left: above,
+ right: upperLeft));
+ }
+#endif
}
}
diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs
index cb4cfb471f..116154836e 100644
--- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs
@@ -2,9 +2,15 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+#if SUPPORTS_RUNTIME_INTRINSICS
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
+
namespace SixLabors.ImageSharp.Formats.Png.Filters
{
///
@@ -64,6 +70,49 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
sum += Numerics.Abs(unchecked((sbyte)res));
}
+#if SUPPORTS_RUNTIME_INTRINSICS
+ if (Avx2.IsSupported)
+ {
+ Vector256 zero = Vector256.Zero;
+ Vector256 sumAccumulator = Vector256.Zero;
+
+ for (int xLeft = x - bytesPerPixel; x + Vector256.Count <= scanline.Length; xLeft += Vector256.Count)
+ {
+ Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x));
+ Vector256 prev = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft));
+
+ Vector256 res = Avx2.Subtract(scan, prev);
+ Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
+ x += Vector256.Count;
+
+ sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32());
+ }
+
+ sum += Numerics.EvenReduceSum(sumAccumulator);
+ }
+ else if (Vector.IsHardwareAccelerated)
+ {
+ Vector sumAccumulator = Vector.Zero;
+
+ for (int xLeft = x - bytesPerPixel; x + Vector.Count <= scanline.Length; xLeft += Vector.Count)
+ {
+ Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x));
+ Vector prev = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft));
+
+ Vector res = scan - prev;
+ Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
+ x += Vector.Count;
+
+ Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res))));
+ }
+
+ for (int i = 0; i < Vector.Count; i++)
+ {
+ sum += (int)sumAccumulator[i];
+ }
+ }
+#endif
+
for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs
index cf553cbb68..e0f35293a4 100644
--- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs
@@ -1,10 +1,16 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+#if SUPPORTS_RUNTIME_INTRINSICS
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
+
namespace SixLabors.ImageSharp.Formats.Png.Filters
{
///
@@ -57,7 +63,52 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
// Up(x) = Raw(x) - Prior(x)
resultBaseRef = 2;
- for (int x = 0; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */)
+ int x = 0;
+
+#if SUPPORTS_RUNTIME_INTRINSICS
+ if (Avx2.IsSupported)
+ {
+ Vector256 zero = Vector256.Zero;
+ Vector256 sumAccumulator = Vector256.Zero;
+
+ for (; x + Vector256.Count <= scanline.Length;)
+ {
+ Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x));
+ Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x));
+
+ Vector256 res = Avx2.Subtract(scan, above);
+ Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
+ x += Vector256.Count;
+
+ sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32());
+ }
+
+ sum += Numerics.EvenReduceSum(sumAccumulator);
+ }
+ else if (Vector.IsHardwareAccelerated)
+ {
+ Vector sumAccumulator = Vector.Zero;
+
+ for (; x + Vector.Count <= scanline.Length;)
+ {
+ Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x));
+ Vector above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x));
+
+ Vector res = scan - above;
+ Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
+ x += Vector.Count;
+
+ Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res))));
+ }
+
+ for (int i = 0; i < Vector.Count; i++)
+ {
+ sum += (int)sumAccumulator[i];
+ }
+ }
+#endif
+
+ for (; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = Unsafe.Add(ref prevBaseRef, x);
diff --git a/src/ImageSharp/Formats/Png/PngCompressionLevel.cs b/src/ImageSharp/Formats/Png/PngCompressionLevel.cs
index 7516e0987d..961f9b05b8 100644
--- a/src/ImageSharp/Formats/Png/PngCompressionLevel.cs
+++ b/src/ImageSharp/Formats/Png/PngCompressionLevel.cs
@@ -1,11 +1,14 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
+using System.ComponentModel;
+
namespace SixLabors.ImageSharp.Formats.Png
{
///
/// Provides enumeration of available PNG compression levels.
///
+ [EditorBrowsable(EditorBrowsableState.Never)]
public enum PngCompressionLevel
{
///
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 3fa0e3f586..c2c336c039 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -10,10 +10,9 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
-using System.Threading.Tasks;
+using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Formats.Png.Filters;
-using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 5d2af8ec6a..7a285eb70b 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -8,11 +8,9 @@ using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
-using System.Threading.Tasks;
-using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Formats.Png.Filters;
-using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
diff --git a/src/ImageSharp/Formats/README.md b/src/ImageSharp/Formats/Tga/README.md
similarity index 78%
rename from src/ImageSharp/Formats/README.md
rename to src/ImageSharp/Formats/Tga/README.md
index 4a2b401b1d..219f111b9d 100644
--- a/src/ImageSharp/Formats/README.md
+++ b/src/ImageSharp/Formats/Tga/README.md
@@ -1,4 +1,4 @@
-# Encoder/Decoder for true vision targa files
+# Encoder/Decoder for true vision targa files
Useful links for reference:
diff --git a/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs b/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs
new file mode 100644
index 0000000000..08d1475268
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats.Tiff.Compression
+{
+ internal static class BitWriterUtils
+ {
+ public static void WriteBits(Span buffer, int pos, uint count, byte value)
+ {
+ int bitPos = pos % 8;
+ int bufferPos = pos / 8;
+ int startIdx = bufferPos + bitPos;
+ int endIdx = (int)(startIdx + count);
+
+ if (value == 1)
+ {
+ for (int i = startIdx; i < endIdx; i++)
+ {
+ WriteBit(buffer, bufferPos, bitPos);
+
+ bitPos++;
+ if (bitPos >= 8)
+ {
+ bitPos = 0;
+ bufferPos++;
+ }
+ }
+ }
+ else
+ {
+ for (int i = startIdx; i < endIdx; i++)
+ {
+ WriteZeroBit(buffer, bufferPos, bitPos);
+
+ bitPos++;
+ if (bitPos >= 8)
+ {
+ bitPos = 0;
+ bufferPos++;
+ }
+ }
+ }
+ }
+
+ public static void WriteBit(Span buffer, int bufferPos, int bitPos) => buffer[bufferPos] |= (byte)(1 << (7 - bitPos));
+
+ public static void WriteZeroBit(Span buffer, int bufferPos, int bitPos) => buffer[bufferPos] = (byte)(buffer[bufferPos] & ~(1 << (7 - bitPos)));
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs
new file mode 100644
index 0000000000..225036f909
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.IO;
+using SixLabors.ImageSharp.Compression.Zlib;
+using SixLabors.ImageSharp.Formats.Tiff.Constants;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
+{
+ internal sealed class DeflateCompressor : TiffBaseCompressor
+ {
+ private readonly DeflateCompressionLevel compressionLevel;
+
+ private readonly MemoryStream memoryStream = new MemoryStream();
+
+ public DeflateCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor, DeflateCompressionLevel compressionLevel)
+ : base(output, allocator, width, bitsPerPixel, predictor)
+ => this.compressionLevel = compressionLevel;
+
+ ///
+ public override TiffCompression Method => TiffCompression.Deflate;
+
+ ///
+ public override void Initialize(int rowsPerStrip)
+ {
+ }
+
+ ///
+ public override void CompressStrip(Span rows, int height)
+ {
+ this.memoryStream.Seek(0, SeekOrigin.Begin);
+ using (var stream = new ZlibDeflateStream(this.Allocator, this.memoryStream, this.compressionLevel))
+ {
+ if (this.Predictor == TiffPredictor.Horizontal)
+ {
+ HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel);
+ }
+
+ stream.Write(rows);
+ stream.Flush();
+ }
+
+ int size = (int)this.memoryStream.Position;
+
+#if !NETSTANDARD1_3
+ byte[] buffer = this.memoryStream.GetBuffer();
+ this.Output.Write(buffer, 0, size);
+#else
+ this.memoryStream.SetLength(size);
+ this.memoryStream.Position = 0;
+ this.memoryStream.CopyTo(this.Output);
+#endif
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs
new file mode 100644
index 0000000000..d2ae9096e2
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.IO;
+using SixLabors.ImageSharp.Formats.Tiff.Constants;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
+{
+ internal sealed class LzwCompressor : TiffBaseCompressor
+ {
+ private TiffLzwEncoder lzwEncoder;
+
+ public LzwCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor)
+ : base(output, allocator, width, bitsPerPixel, predictor)
+ {
+ }
+
+ ///
+ public override TiffCompression Method => TiffCompression.Lzw;
+
+ ///
+ public override void Initialize(int rowsPerStrip) => this.lzwEncoder = new TiffLzwEncoder(this.Allocator);
+
+ ///
+ public override void CompressStrip(Span rows, int height)
+ {
+ if (this.Predictor == TiffPredictor.Horizontal)
+ {
+ HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel);
+ }
+
+ this.lzwEncoder.Encode(rows, this.Output);
+ }
+
+ ///
+ protected override void Dispose(bool disposing) => this.lzwEncoder?.Dispose();
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs
new file mode 100644
index 0000000000..79bb2e98f8
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.IO;
+using SixLabors.ImageSharp.Formats.Tiff.Constants;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
+{
+ internal sealed class NoCompressor : TiffBaseCompressor
+ {
+ public NoCompressor(Stream output, MemoryAllocator memoryAllocator, int width, int bitsPerPixel)
+ : base(output, memoryAllocator, width, bitsPerPixel)
+ {
+ }
+
+ ///
+ public override TiffCompression Method => TiffCompression.None;
+
+ ///
+ public override void Initialize(int rowsPerStrip)
+ {
+ }
+
+ ///
+ public override void CompressStrip(Span rows, int height) => this.Output.Write(rows);
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs
new file mode 100644
index 0000000000..5a23831878
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.IO;
+using SixLabors.ImageSharp.Formats.Tiff.Constants;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
+{
+ internal sealed class PackBitsCompressor : TiffBaseCompressor
+ {
+ private IManagedByteBuffer pixelData;
+
+ public PackBitsCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel)
+ : base(output, allocator, width, bitsPerPixel)
+ {
+ }
+
+ ///
+ public override TiffCompression Method => TiffCompression.PackBits;
+
+ ///
+ public override void Initialize(int rowsPerStrip)
+ {
+ int additionalBytes = ((this.BytesPerRow + 126) / 127) + 1;
+ this.pixelData = this.Allocator.AllocateManagedByteBuffer(this.BytesPerRow + additionalBytes);
+ }
+
+ ///
+ public override void CompressStrip(Span rows, int height)
+ {
+ DebugGuard.IsTrue(rows.Length % height == 0, "Invalid height");
+ DebugGuard.IsTrue(this.BytesPerRow == rows.Length / height, "The widths must match");
+
+ Span span = this.pixelData.GetSpan();
+ for (int i = 0; i < height; i++)
+ {
+ Span row = rows.Slice(i * this.BytesPerRow, this.BytesPerRow);
+ int size = PackBitsWriter.PackBits(row, span);
+ this.Output.Write(span.Slice(0, size));
+ }
+ }
+
+ ///
+ protected override void Dispose(bool disposing) => this.pixelData?.Dispose();
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs
new file mode 100644
index 0000000000..30d21e54ce
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs
@@ -0,0 +1,128 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
+{
+ ///
+ /// Pack Bits compression for tiff images. See Tiff Spec v6, section 9.
+ ///
+ internal static class PackBitsWriter
+ {
+ public static int PackBits(ReadOnlySpan rowSpan, Span compressedRowSpan)
+ {
+ int maxRunLength = 127;
+ int posInRowSpan = 0;
+ int bytesWritten = 0;
+ int literalRunLength = 0;
+
+ while (posInRowSpan < rowSpan.Length)
+ {
+ bool useReplicateRun = IsReplicateRun(rowSpan, posInRowSpan);
+ if (useReplicateRun)
+ {
+ if (literalRunLength > 0)
+ {
+ WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten);
+ bytesWritten += literalRunLength + 1;
+ }
+
+ // Write a run with the same bytes.
+ int runLength = FindRunLength(rowSpan, posInRowSpan, maxRunLength);
+ WriteRun(rowSpan, posInRowSpan, runLength, compressedRowSpan, bytesWritten);
+
+ bytesWritten += 2;
+ literalRunLength = 0;
+ posInRowSpan += runLength;
+ continue;
+ }
+
+ literalRunLength++;
+ posInRowSpan++;
+
+ if (literalRunLength >= maxRunLength)
+ {
+ WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten);
+ bytesWritten += literalRunLength + 1;
+ literalRunLength = 0;
+ }
+ }
+
+ if (literalRunLength > 0)
+ {
+ WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten);
+ bytesWritten += literalRunLength + 1;
+ }
+
+ return bytesWritten;
+ }
+
+ private static void WriteLiteralRun(ReadOnlySpan rowSpan, int end, int literalRunLength, Span compressedRowSpan, int compressedRowPos)
+ {
+ DebugGuard.MustBeLessThanOrEqualTo(literalRunLength, 127, nameof(literalRunLength));
+
+ int literalRunStart = end - literalRunLength;
+ sbyte runLength = (sbyte)(literalRunLength - 1);
+ compressedRowSpan[compressedRowPos] = (byte)runLength;
+ rowSpan.Slice(literalRunStart, literalRunLength).CopyTo(compressedRowSpan.Slice(compressedRowPos + 1));
+ }
+
+ private static void WriteRun(ReadOnlySpan rowSpan, int start, int runLength, Span compressedRowSpan, int compressedRowPos)
+ {
+ DebugGuard.MustBeLessThanOrEqualTo(runLength, 127, nameof(runLength));
+
+ sbyte headerByte = (sbyte)(-runLength + 1);
+ compressedRowSpan[compressedRowPos] = (byte)headerByte;
+ compressedRowSpan[compressedRowPos + 1] = rowSpan[start];
+ }
+
+ private static bool IsReplicateRun(ReadOnlySpan rowSpan, int startPos)
+ {
+ // We consider run which has at least 3 same consecutive bytes a candidate for a run.
+ var startByte = rowSpan[startPos];
+ int count = 0;
+ for (int i = startPos + 1; i < rowSpan.Length; i++)
+ {
+ if (rowSpan[i] == startByte)
+ {
+ count++;
+ if (count >= 2)
+ {
+ return true;
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ private static int FindRunLength(ReadOnlySpan rowSpan, int startPos, int maxRunLength)
+ {
+ var startByte = rowSpan[startPos];
+ int count = 1;
+ for (int i = startPos + 1; i < rowSpan.Length; i++)
+ {
+ if (rowSpan[i] == startByte)
+ {
+ count++;
+ }
+ else
+ {
+ break;
+ }
+
+ if (count == maxRunLength)
+ {
+ break;
+ }
+ }
+
+ return count;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs
new file mode 100644
index 0000000000..3e9b7f4e63
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs
@@ -0,0 +1,592 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.IO;
+using SixLabors.ImageSharp.Formats.Tiff.Constants;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
+{
+ ///
+ /// Bitwriter for writing compressed CCITT T4 1D data.
+ ///
+ internal sealed class T4BitCompressor : TiffBaseCompressor
+ {
+ private const uint WhiteZeroRunTermCode = 0x35;
+
+ private const uint BlackZeroRunTermCode = 0x37;
+
+ private static readonly uint[] MakeupRunLength =
+ {
+ 64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560
+ };
+
+ private static readonly Dictionary WhiteLen4TermCodes = new Dictionary()
+ {
+ { 2, 0x7 }, { 3, 0x8 }, { 4, 0xB }, { 5, 0xC }, { 6, 0xE }, { 7, 0xF }
+ };
+
+ private static readonly Dictionary WhiteLen5TermCodes = new Dictionary()
+ {
+ { 8, 0x13 }, { 9, 0x14 }, { 10, 0x7 }, { 11, 0x8 }
+ };
+
+ private static readonly Dictionary WhiteLen6TermCodes = new Dictionary()
+ {
+ { 1, 0x7 }, { 12, 0x8 }, { 13, 0x3 }, { 14, 0x34 }, { 15, 0x35 }, { 16, 0x2A }, { 17, 0x2B }
+ };
+
+ private static readonly Dictionary WhiteLen7TermCodes = new Dictionary