Browse Source

Add SaveAsWebP methods and tests

pull/1552/head
Brian Popow 5 years ago
parent
commit
3d280757ef
  1. 107
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  2. 28
      src/ImageSharp/Formats/ImageExtensions.Save.tt
  3. 6
      src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
  4. 10
      src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs
  5. 164
      tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTest.cs
  6. 1
      tests/ImageSharp.Tests/Formats/WebP/WebPDecoderTests.cs
  7. 1
      tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs
  8. 1
      tests/ImageSharp.Tests/Formats/WebP/WebPMetaDataTests.cs

107
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.
// <auto-generated />
@ -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);
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the WebP format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsWebP(this Image source, string path) => SaveAsWebP(source, path, null);
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the WebP format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsWebPAsync(this Image source, string path) => SaveAsWebPAsync(source, path, null);
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the WebP format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsWebPAsync(this Image source, string path, CancellationToken cancellationToken)
=> SaveAsWebPAsync(source, path, null, cancellationToken);
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the WebP format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsWebP(this Image source, string path, WebPEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebPFormat.Instance));
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the WebP format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
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);
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the WebP format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsWebP(this Image source, Stream stream)
=> SaveAsWebP(source, stream, null);
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the WebP format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsWebPAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
=> SaveAsWebPAsync(source, stream, null, cancellationToken);
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the WebP format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static void SaveAsWebP(this Image source, Stream stream, WebPEncoder encoder)
=> source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebPFormat.Instance));
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the WebP format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
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);
}
}

28
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! " : "";
#>
/// <summary>
/// Saves the image to the given stream with the <#= fmt #> format.
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
@ -48,7 +56,7 @@ namespace SixLabors.ImageSharp
public static void SaveAs<#= fmt #>(this Image source, string path) => SaveAs<#= fmt #>(source, path, null);
/// <summary>
/// Saves the image to the given stream with the <#= fmt #> format.
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
@ -57,7 +65,7 @@ namespace SixLabors.ImageSharp
public static Task SaveAs<#= fmt #>Async(this Image source, string path) => SaveAs<#= fmt #>Async(source, path, null);
/// <summary>
/// Saves the image to the given stream with the <#= fmt #> format.
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
@ -68,7 +76,7 @@ namespace SixLabors.ImageSharp
=> SaveAs<#= fmt #>Async(source, path, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the <#= fmt #> format.
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
@ -80,7 +88,7 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance));
/// <summary>
/// Saves the image to the given stream with the <#= fmt #> format.
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
@ -95,7 +103,7 @@ namespace SixLabors.ImageSharp
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the <#= fmt #> format.
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
@ -104,7 +112,7 @@ namespace SixLabors.ImageSharp
=> SaveAs<#= fmt #>(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the <#= fmt #> format.
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
@ -115,7 +123,7 @@ namespace SixLabors.ImageSharp
=> SaveAs<#= fmt #>Async(source, stream, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the <#= fmt #> format.
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
@ -128,7 +136,7 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance));
/// <summary>
/// Saves the image to the given stream with the <#= fmt #> format.
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>

6
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);
}

10
src/ImageSharp/Formats/WebP/Lossless/WebPLosslessDecoder.cs

@ -196,15 +196,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.WebP.Lossless
ApplyInverseTransforms(decoder, pixelData, this.memoryAllocator);
Span<byte> pixelDataAsBytes = MemoryMarshal.Cast<uint, byte>(pixelData);
int widthMul4 = width * 4;
int bytesPerRow = width * 4;
for (int y = 0; y < decoder.Height; y++)
{
Span<byte> row = pixelDataAsBytes.Slice(y * widthMul4, widthMul4);
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
Span<byte> rowAsBytes = pixelDataAsBytes.Slice(y * bytesPerRow, bytesPerRow);
Span<TPixel> pixelRow = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.configuration,
row,
pixelSpan,
rowAsBytes.Slice(0, bytesPerRow),
pixelRow.Slice(0, width),
width);
}
}

164
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<Rgba32>(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<Rgba32>(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<Rgba32>(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<Rgba32>(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<Rgba32>(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<Rgba32>(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<Rgba32>(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<Rgba32>(10, 10))
{
await image.SaveAsWebPAsync(memoryStream, new WebPEncoder());
}
memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime))
{
Assert.Equal("image/webp", mime.DefaultMimeType);
}
}
}
}

1
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();

1
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]

1
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]

Loading…
Cancel
Save