diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs
index 075c708b6..f7900ec73 100644
--- a/src/ImageSharp/Formats/ImageExtensions.Save.cs
+++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
//
@@ -6,6 +6,8 @@ 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;
@@ -535,5 +537,108 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance),
cancellationToken);
+ ///
+ /// EXPERIMENTAL! Saves the image to the given stream with the WebP format.
+ ///
+ /// The image this method extends.
+ /// The file path to save the image to.
+ /// Thrown if the path is null.
+ 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.
+ ///
+ /// 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 SaveAsWebPAsync(this Image source, string path) => SaveAsWebPAsync(source, path, null);
+
+ ///
+ /// EXPERIMENTAL! Saves the image to the given stream with the WebP 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 SaveAsWebPAsync(this Image source, string path, CancellationToken cancellationToken)
+ => SaveAsWebPAsync(source, path, null, cancellationToken);
+
+ ///
+ /// EXPERIMENTAL! Saves the image to the given stream with the WebP 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 SaveAsWebP(this Image source, string path, WebPEncoder encoder) =>
+ source.Save(
+ path,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebPFormat.Instance));
+
+ ///
+ /// EXPERIMENTAL! Saves the image to the given stream with the WebP 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 SaveAsWebPAsync(this Image source, string path, WebPEncoder encoder, CancellationToken cancellationToken = default) =>
+ source.SaveAsync(
+ path,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebPFormat.Instance),
+ cancellationToken);
+
+ ///
+ /// EXPERIMENTAL! Saves the image to the given stream with the WebP format.
+ ///
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// Thrown if the stream is null.
+ public static void SaveAsWebP(this Image source, Stream stream)
+ => SaveAsWebP(source, stream, null);
+
+ ///
+ /// EXPERIMENTAL! Saves the image to the given stream with the WebP 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 SaveAsWebPAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
+ => SaveAsWebPAsync(source, stream, null, cancellationToken);
+
+ ///
+ /// EXPERIMENTAL! Saves the image to the given stream with the WebP 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 SaveAsWebP(this Image source, Stream stream, WebPEncoder encoder)
+ => source.Save(
+ stream,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebPFormat.Instance));
+
+ ///
+ /// EXPERIMENTAL! Saves the image to the given stream with the WebP 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 SaveAsWebPAsync(this Image source, Stream stream, WebPEncoder encoder, CancellationToken cancellationToken = default) =>
+ source.SaveAsync(
+ stream,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebPFormat.Instance),
+ cancellationToken);
+
}
}
diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.tt b/src/ImageSharp/Formats/ImageExtensions.Save.tt
index 63b404cc4..c8979da0d 100644
--- a/src/ImageSharp/Formats/ImageExtensions.Save.tt
+++ b/src/ImageSharp/Formats/ImageExtensions.Save.tt
@@ -1,4 +1,4 @@
-<#@ template language="C#" #>
+<#@ template language="C#" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
// Copyright (c) Six Labors.
@@ -9,6 +9,8 @@ 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 []{
@@ -17,10 +19,15 @@ using SixLabors.ImageSharp.Advanced;
"Jpeg",
"Png",
"Tga",
+ "WebP"
};
foreach (string fmt in formats)
{
+ if (fmt == "Tiff" || fmt == "WebP")
+ {
+ continue;
+ }
#>
using SixLabors.ImageSharp.Formats.<#= fmt #>;
<#
@@ -38,9 +45,10 @@ namespace SixLabors.ImageSharp
<#
foreach (string fmt in formats)
{
+ string experimentalString = fmt == "Tiff" || fmt == "WebP" ? @"EXPERIMENTAL! " : "";
#>
///
- /// Saves the image to the given stream with the <#= fmt #> format.
+ /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -48,7 +56,7 @@ namespace SixLabors.ImageSharp
public static void SaveAs<#= fmt #>(this Image source, string path) => SaveAs<#= fmt #>(source, path, null);
///
- /// Saves the image to the given stream with the <#= fmt #> format.
+ /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -57,7 +65,7 @@ namespace SixLabors.ImageSharp
public static Task SaveAs<#= fmt #>Async(this Image source, string path) => SaveAs<#= fmt #>Async(source, path, null);
///
- /// Saves the image to the given stream with the <#= fmt #> format.
+ /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -68,7 +76,7 @@ namespace SixLabors.ImageSharp
=> SaveAs<#= fmt #>Async(source, path, null, cancellationToken);
///
- /// Saves the image to the given stream with the <#= fmt #> format.
+ /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -80,7 +88,7 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance));
///
- /// Saves the image to the given stream with the <#= fmt #> format.
+ /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -95,7 +103,7 @@ namespace SixLabors.ImageSharp
cancellationToken);
///
- /// Saves the image to the given stream with the <#= fmt #> format.
+ /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The stream to save the image to.
@@ -104,7 +112,7 @@ namespace SixLabors.ImageSharp
=> SaveAs<#= fmt #>(source, stream, null);
///
- /// Saves the image to the given stream with the <#= fmt #> format.
+ /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The stream to save the image to.
@@ -115,7 +123,7 @@ namespace SixLabors.ImageSharp
=> SaveAs<#= fmt #>Async(source, stream, null, cancellationToken);
///
- /// Saves the image to the given stream with the <#= fmt #> format.
+ /// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
///
/// The image this method extends.
/// The stream to save the image to.
@@ -128,7 +136,7 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance));
///
- /// Saves the image to the given stream with the <#= fmt #> format.
+ /// <#= experimentalString #>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/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
index 3fac4eb45..9782d8ab7 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
@@ -423,6 +423,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless
Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter;
Vp8LBitWriter bwInit = this.bitWriter;
+ bool isFirstIteration = true;
foreach (CrunchSubConfig subConfig in config.SubConfigs)
{
Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences(
@@ -525,11 +526,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless
this.StoreImageToBitMask(width, histogramBits, refsBest, histogramSymbols, huffmanCodes);
// Keep track of the smallest image so far.
- if (bitWriterBest != null && this.bitWriter.NumBytes() < bitWriterBest.NumBytes())
+ if (isFirstIteration || (bitWriterBest != null && this.bitWriter.NumBytes() < bitWriterBest.NumBytes()))
{
// TODO: This was done in the reference by swapping references, this will be slower
bitWriterBest = this.bitWriter.Clone();
}
+
+ isFirstIteration = false;
}
this.bitWriter = bitWriterBest;
@@ -1156,6 +1159,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless
xBits = (paletteSize <= 16) ? 1 : 0;
}
+ this.CurrentWidth = LosslessUtils.SubSampleSize(width, xBits);
this.ApplyPalette(src, srcStride, dst, this.CurrentWidth, palette, paletteSize, width, height, xBits);
}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs
index 0ff3cccbc..8a6c36595 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs
@@ -196,15 +196,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless
ApplyInverseTransforms(decoder, pixelData, this.memoryAllocator);
Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData);
- int widthMul4 = width * 4;
+ int bytesPerRow = width * 4;
for (int y = 0; y < decoder.Height; y++)
{
- Span row = pixelDataAsBytes.Slice(y * widthMul4, widthMul4);
- Span pixelSpan = pixels.GetRowSpan(y);
+ Span rowAsBytes = pixelDataAsBytes.Slice(y * bytesPerRow, bytesPerRow);
+ Span pixelRow = pixels.GetRowSpan(y);
PixelOperations.Instance.FromBgra32Bytes(
this.configuration,
- row,
- pixelSpan,
+ rowAsBytes.Slice(0, bytesPerRow),
+ pixelRow.Slice(0, width),
width);
}
}
diff --git a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs
new file mode 100644
index 000000000..b20376b47
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs
@@ -0,0 +1,164 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+using System.Threading.Tasks;
+using SixLabors.ImageSharp.Formats;
+using SixLabors.ImageSharp.Formats.Experimental.WebP;
+using SixLabors.ImageSharp.PixelFormats;
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Webp
+{
+ [Trait("Format", "Webp")]
+ public class ImageExtensionsTest
+ {
+ public ImageExtensionsTest()
+ {
+ Configuration.Default.ImageFormatsManager.AddImageFormat(WebPFormat.Instance);
+ Configuration.Default.ImageFormatsManager.AddImageFormatDetector(new WebPImageFormatDetector());
+ Configuration.Default.ImageFormatsManager.SetDecoder(WebPFormat.Instance, new WebPDecoder());
+ Configuration.Default.ImageFormatsManager.SetEncoder(WebPFormat.Instance, new WebPEncoder());
+ }
+
+ [Fact]
+ public void SaveAsWebp_Path()
+ {
+ string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
+ string file = Path.Combine(dir, "SaveAsWebp_Path.webp");
+
+ using (var image = new Image(10, 10))
+ {
+ image.SaveAsWebP(file);
+ }
+
+ using (Image.Load(file, out IImageFormat mime))
+ {
+ Assert.Equal("image/webp", mime.DefaultMimeType);
+ }
+ }
+
+ [Fact]
+ public async Task SaveAsWebpAsync_Path()
+ {
+ string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
+ string file = Path.Combine(dir, "SaveAsWebpAsync_Path.webp");
+
+ using (var image = new Image(10, 10))
+ {
+ await image.SaveAsWebPAsync(file);
+ }
+
+ using (Image.Load(file, out IImageFormat mime))
+ {
+ Assert.Equal("image/webp", mime.DefaultMimeType);
+ }
+ }
+
+ [Fact]
+ public void SaveAsWebp_Path_Encoder()
+ {
+ string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
+ string file = Path.Combine(dir, "SaveAsWebp_Path_Encoder.webp");
+
+ using (var image = new Image(10, 10))
+ {
+ image.SaveAsWebP(file, new WebPEncoder());
+ }
+
+ using (Image.Load(file, out IImageFormat mime))
+ {
+ Assert.Equal("image/webp", mime.DefaultMimeType);
+ }
+ }
+
+ [Fact]
+ public async Task SaveAsWebpAsync_Path_Encoder()
+ {
+ string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
+ string file = Path.Combine(dir, "SaveAsWebpAsync_Path_Encoder.webp");
+
+ using (var image = new Image(10, 10))
+ {
+ await image.SaveAsWebPAsync(file, new WebPEncoder());
+ }
+
+ using (Image.Load(file, out IImageFormat mime))
+ {
+ Assert.Equal("image/webp", mime.DefaultMimeType);
+ }
+ }
+
+ [Fact]
+ public void SaveAsWebp_Stream()
+ {
+ using var memoryStream = new MemoryStream();
+
+ using (var image = new Image(10, 10))
+ {
+ image.SaveAsWebP(memoryStream);
+ }
+
+ memoryStream.Position = 0;
+
+ using (Image.Load(memoryStream, out IImageFormat mime))
+ {
+ Assert.Equal("image/webp", mime.DefaultMimeType);
+ }
+ }
+
+ [Fact]
+ public async Task SaveAsWebpAsync_StreamAsync()
+ {
+ using var memoryStream = new MemoryStream();
+
+ using (var image = new Image(10, 10))
+ {
+ await image.SaveAsWebPAsync(memoryStream);
+ }
+
+ memoryStream.Position = 0;
+
+ using (Image.Load(memoryStream, out IImageFormat mime))
+ {
+ Assert.Equal("image/webp", mime.DefaultMimeType);
+ }
+ }
+
+ [Fact]
+ public void SaveAsWebp_Stream_Encoder()
+ {
+ using var memoryStream = new MemoryStream();
+
+ using (var image = new Image(10, 10))
+ {
+ image.SaveAsWebp(memoryStream, new WebPEncoder());
+ }
+
+ memoryStream.Position = 0;
+
+ using (Image.Load(memoryStream, out IImageFormat mime))
+ {
+ Assert.Equal("image/webp", mime.DefaultMimeType);
+ }
+ }
+
+ [Fact]
+ public async Task SaveAsWebpAsync_Stream_Encoder()
+ {
+ using var memoryStream = new MemoryStream();
+
+ using (var image = new Image(10, 10))
+ {
+ await image.SaveAsWebPAsync(memoryStream, new WebPEncoder());
+ }
+
+ memoryStream.Position = 0;
+
+ using (Image.Load(memoryStream, out IImageFormat mime))
+ {
+ Assert.Equal("image/webp", mime.DefaultMimeType);
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs
index 938ba2e7d..562befa21 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs
@@ -14,6 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP
{
using static SixLabors.ImageSharp.Tests.TestImages.WebP;
+ [Trait("Format", "Webp")]
public class WebPDecoderTests
{
private static WebPDecoder WebpDecoder => new WebPDecoder();
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs
index 3e4b0799b..8143503a1 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs
@@ -10,6 +10,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP
{
using static TestImages.WebP;
+ [Trait("Format", "Webp")]
public class WebPEncoderTests
{
[Theory]
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs
index 4eff21572..375770117 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs
@@ -8,6 +8,7 @@ using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.WebP
{
+ [Trait("Format", "Webp")]
public class WebPMetadataTests
{
[Theory]