Browse Source

Implement Vp8L encoder

pull/2569/head
Poker 3 years ago
parent
commit
87de521412
No known key found for this signature in database GPG Key ID: C65A6AD457D5C8F8
  1. 87
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  2. 32
      src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
  3. 7
      tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs

87
src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

@ -10,7 +10,6 @@ using SixLabors.ImageSharp.Formats.Webp.BitWriter;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
using SixLabors.ImageSharp.PixelFormats;
@ -236,26 +235,59 @@ internal class Vp8LEncoder : IDisposable
/// </summary>
public Vp8LHashChain HashChain { get; }
/// <summary>
/// Encodes the image as lossless webp to the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAnimation, uint background = 0, uint loopCount = 0)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
// Write bytes from the bitwriter buffer to the stream.
ImageMetadata metadata = image.Metadata;
metadata.SyncProfiles();
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
BitWriterBase.WriteTrunksBeforeData(
stream,
(uint)image.Width,
(uint)image.Height,
exifProfile,
xmpProfile,
metadata.IccProfile,
false,
hasAnimation);
if (hasAnimation)
{
BitWriterBase.WriteAnimationParameter(stream, background, (ushort)loopCount);
}
}
public void EncodeFooter<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
// Write bytes from the bitwriter buffer to the stream.
ImageMetadata metadata = image.Metadata;
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
BitWriterBase.WriteTrunksAfterData(stream, exifProfile, xmpProfile);
}
/// <summary>
/// Encodes the image as lossless webp to the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frame">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
public void Encode<TPixel>(ImageFrame<TPixel> frame, Stream stream, bool hasAnimation)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = frame.Width;
int height = frame.Height;
// Convert image pixels to bgra array.
bool hasAlpha = this.ConvertPixelsToBgra(image.Frames.RootFrame, width, height);
bool hasAlpha = this.ConvertPixelsToBgra(frame, width, height);
// Write the image size.
this.WriteImageSize(width, height);
@ -264,23 +296,28 @@ internal class Vp8LEncoder : IDisposable
this.WriteAlphaAndVersion(hasAlpha);
// Encode the main image stream.
this.EncodeStream(image.Frames.RootFrame);
this.EncodeStream(frame);
this.bitWriter.Finish();
BitWriterBase.WriteTrunksBeforeData(
stream,
(uint)width,
(uint)height,
exifProfile,
xmpProfile,
metadata.IccProfile,
false /*hasAlpha*/,
false);
long prevPosition = 0;
if (hasAnimation)
{
prevPosition = BitWriterBase.WriteAnimationFrame(stream, new()
{
Width = (uint)frame.Width,
Height = (uint)frame.Height
});
}
// Write bytes from the bitwriter buffer to the stream.
this.bitWriter.WriteEncodedImageToStream(stream);
BitWriterBase.WriteTrunksAfterData(stream, exifProfile, xmpProfile);
if (hasAnimation)
{
BitWriterBase.OverwriteFrameSize(stream, prevPosition);
}
}
/// <summary>
@ -1843,9 +1880,9 @@ internal class Vp8LEncoder : IDisposable
{
this.Bgra.Dispose();
this.EncodedData.Dispose();
this.BgraScratch.Dispose();
this.BgraScratch?.Dispose();
this.Palette.Dispose();
this.TransformData.Dispose();
this.TransformData?.Dispose();
this.HashChain.Dispose();
}

32
src/ImageSharp/Formats/Webp/WebpEncoderCore.cs

@ -129,7 +129,7 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
if (lossless)
{
using Vp8LEncoder enc = new(
using Vp8LEncoder encoder = new(
this.memoryAllocator,
this.configuration,
image.Width,
@ -140,7 +140,34 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
this.transparentColorMode,
this.nearLossless,
this.nearLosslessQuality);
enc.Encode(image, stream);
bool hasAnimation = image.Frames.Count > 1;
encoder.EncodeHeader(image, stream, hasAnimation);
if (hasAnimation)
{
foreach (ImageFrame<TPixel> imageFrame in image.Frames)
{
using Vp8LEncoder enc = new(
this.memoryAllocator,
this.configuration,
image.Width,
image.Height,
this.quality,
this.skipMetadata,
this.method,
this.transparentColorMode,
this.nearLossless,
this.nearLosslessQuality);
enc.Encode(imageFrame, stream, true);
}
}
else
{
encoder.Encode(image.Frames.RootFrame, stream, false);
}
encoder.EncodeFooter(image, stream);
}
else
{
@ -174,6 +201,7 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
this.filterStrength,
this.spatialNoiseShaping,
this.alphaCompression);
enc.EncodeAnimation(imageFrame, stream);
}
}

7
tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs

@ -20,8 +20,11 @@ public class WebpEncoderTests
[Fact]
public void Encode_AnimatedLossy()
{
Image<Rgba32> image = Image.Load<Rgba32>(@"C:\Users\poker\Desktop\1.webp");
image.SaveAsWebp(@"C:\Users\poker\Desktop\3.webp");
Image<Rgba32> image = Image.Load<Rgba32>(@"C:\WorkSpace\ImageSharp\tests\Images\Input\Webp\leo_animated_lossless.webp");
image.SaveAsWebp(@"C:\Users\poker\Desktop\3.webp", new WebpEncoder()
{
FileFormat = WebpFileFormatType.Lossless
});
}
[Theory]

Loading…
Cancel
Save