Skip to content

Commit 87de521

Browse files
committed
Implement Vp8L encoder
1 parent fbc08bd commit 87de521

File tree

3 files changed

+97
-29
lines changed

3 files changed

+97
-29
lines changed

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

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
using SixLabors.ImageSharp.Memory;
1111
using SixLabors.ImageSharp.Metadata;
1212
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
13-
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
1413
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
1514
using SixLabors.ImageSharp.PixelFormats;
1615

@@ -236,26 +235,59 @@ public Vp8LEncoder(
236235
/// </summary>
237236
public Vp8LHashChain HashChain { get; }
238237

239-
/// <summary>
240-
/// Encodes the image as lossless webp to the specified stream.
241-
/// </summary>
242-
/// <typeparam name="TPixel">The pixel format.</typeparam>
243-
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
244-
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
245-
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
238+
public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAnimation, uint background = 0, uint loopCount = 0)
246239
where TPixel : unmanaged, IPixel<TPixel>
247240
{
248-
int width = image.Width;
249-
int height = image.Height;
250-
241+
// Write bytes from the bitwriter buffer to the stream.
251242
ImageMetadata metadata = image.Metadata;
252243
metadata.SyncProfiles();
253244

254245
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
255246
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
256247

248+
BitWriterBase.WriteTrunksBeforeData(
249+
stream,
250+
(uint)image.Width,
251+
(uint)image.Height,
252+
exifProfile,
253+
xmpProfile,
254+
metadata.IccProfile,
255+
false,
256+
hasAnimation);
257+
258+
if (hasAnimation)
259+
{
260+
BitWriterBase.WriteAnimationParameter(stream, background, (ushort)loopCount);
261+
}
262+
}
263+
264+
public void EncodeFooter<TPixel>(Image<TPixel> image, Stream stream)
265+
where TPixel : unmanaged, IPixel<TPixel>
266+
{
267+
// Write bytes from the bitwriter buffer to the stream.
268+
ImageMetadata metadata = image.Metadata;
269+
270+
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
271+
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
272+
273+
BitWriterBase.WriteTrunksAfterData(stream, exifProfile, xmpProfile);
274+
}
275+
276+
/// <summary>
277+
/// Encodes the image as lossless webp to the specified stream.
278+
/// </summary>
279+
/// <typeparam name="TPixel">The pixel format.</typeparam>
280+
/// <param name="frame">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
281+
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
282+
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
283+
public void Encode<TPixel>(ImageFrame<TPixel> frame, Stream stream, bool hasAnimation)
284+
where TPixel : unmanaged, IPixel<TPixel>
285+
{
286+
int width = frame.Width;
287+
int height = frame.Height;
288+
257289
// Convert image pixels to bgra array.
258-
bool hasAlpha = this.ConvertPixelsToBgra(image.Frames.RootFrame, width, height);
290+
bool hasAlpha = this.ConvertPixelsToBgra(frame, width, height);
259291

260292
// Write the image size.
261293
this.WriteImageSize(width, height);
@@ -264,23 +296,28 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
264296
this.WriteAlphaAndVersion(hasAlpha);
265297

266298
// Encode the main image stream.
267-
this.EncodeStream(image.Frames.RootFrame);
299+
this.EncodeStream(frame);
268300

269301
this.bitWriter.Finish();
270-
BitWriterBase.WriteTrunksBeforeData(
271-
stream,
272-
(uint)width,
273-
(uint)height,
274-
exifProfile,
275-
xmpProfile,
276-
metadata.IccProfile,
277-
false /*hasAlpha*/,
278-
false);
302+
303+
long prevPosition = 0;
304+
305+
if (hasAnimation)
306+
{
307+
prevPosition = BitWriterBase.WriteAnimationFrame(stream, new()
308+
{
309+
Width = (uint)frame.Width,
310+
Height = (uint)frame.Height
311+
});
312+
}
279313

280314
// Write bytes from the bitwriter buffer to the stream.
281315
this.bitWriter.WriteEncodedImageToStream(stream);
282316

283-
BitWriterBase.WriteTrunksAfterData(stream, exifProfile, xmpProfile);
317+
if (hasAnimation)
318+
{
319+
BitWriterBase.OverwriteFrameSize(stream, prevPosition);
320+
}
284321
}
285322

286323
/// <summary>
@@ -1843,9 +1880,9 @@ public void Dispose()
18431880
{
18441881
this.Bgra.Dispose();
18451882
this.EncodedData.Dispose();
1846-
this.BgraScratch.Dispose();
1883+
this.BgraScratch?.Dispose();
18471884
this.Palette.Dispose();
1848-
this.TransformData.Dispose();
1885+
this.TransformData?.Dispose();
18491886
this.HashChain.Dispose();
18501887
}
18511888

src/ImageSharp/Formats/Webp/WebpEncoderCore.cs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
129129

130130
if (lossless)
131131
{
132-
using Vp8LEncoder enc = new(
132+
using Vp8LEncoder encoder = new(
133133
this.memoryAllocator,
134134
this.configuration,
135135
image.Width,
@@ -140,7 +140,34 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
140140
this.transparentColorMode,
141141
this.nearLossless,
142142
this.nearLosslessQuality);
143-
enc.Encode(image, stream);
143+
144+
bool hasAnimation = image.Frames.Count > 1;
145+
encoder.EncodeHeader(image, stream, hasAnimation);
146+
if (hasAnimation)
147+
{
148+
foreach (ImageFrame<TPixel> imageFrame in image.Frames)
149+
{
150+
using Vp8LEncoder enc = new(
151+
this.memoryAllocator,
152+
this.configuration,
153+
image.Width,
154+
image.Height,
155+
this.quality,
156+
this.skipMetadata,
157+
this.method,
158+
this.transparentColorMode,
159+
this.nearLossless,
160+
this.nearLosslessQuality);
161+
162+
enc.Encode(imageFrame, stream, true);
163+
}
164+
}
165+
else
166+
{
167+
encoder.Encode(image.Frames.RootFrame, stream, false);
168+
}
169+
170+
encoder.EncodeFooter(image, stream);
144171
}
145172
else
146173
{
@@ -174,6 +201,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
174201
this.filterStrength,
175202
this.spatialNoiseShaping,
176203
this.alphaCompression);
204+
177205
enc.EncodeAnimation(imageFrame, stream);
178206
}
179207
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ public class WebpEncoderTests
2020
[Fact]
2121
public void Encode_AnimatedLossy()
2222
{
23-
Image<Rgba32> image = Image.Load<Rgba32>(@"C:\Users\poker\Desktop\1.webp");
24-
image.SaveAsWebp(@"C:\Users\poker\Desktop\3.webp");
23+
Image<Rgba32> image = Image.Load<Rgba32>(@"C:\WorkSpace\ImageSharp\tests\Images\Input\Webp\leo_animated_lossless.webp");
24+
image.SaveAsWebp(@"C:\Users\poker\Desktop\3.webp", new WebpEncoder()
25+
{
26+
FileFormat = WebpFileFormatType.Lossless
27+
});
2528
}
2629

2730
[Theory]

0 commit comments

Comments
 (0)