Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Implement Vp8L encoder
  • Loading branch information
Poker-sang committed Oct 23, 2023
commit 87de52141219f23a39559a8bae09d79c30e109d6
87 changes: 62 additions & 25 deletions src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
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;

Expand Down Expand Up @@ -236,26 +235,59 @@ public Vp8LEncoder(
/// </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);
Expand All @@ -264,23 +296,28 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
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>
Expand Down Expand Up @@ -1843,9 +1880,9 @@ public void Dispose()
{
this.Bgra.Dispose();
this.EncodedData.Dispose();
this.BgraScratch.Dispose();
this.BgraScratch?.Dispose();
this.Palette.Dispose();
this.TransformData.Dispose();
this.TransformData?.Dispose();
this.HashChain.Dispose();
}

Expand Down
32 changes: 30 additions & 2 deletions src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken

if (lossless)
{
using Vp8LEncoder enc = new(
using Vp8LEncoder encoder = new(
this.memoryAllocator,
this.configuration,
image.Width,
Expand All @@ -140,7 +140,34 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
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
{
Expand Down Expand Up @@ -174,6 +201,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
this.filterStrength,
this.spatialNoiseShaping,
this.alphaCompression);

enc.EncodeAnimation(imageFrame, stream);
}
}
Expand Down
7 changes: 5 additions & 2 deletions tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down