Skip to content

Commit cc0727b

Browse files
Add dedup to webp
1 parent a42f6b6 commit cc0727b

9 files changed

Lines changed: 154 additions & 122 deletions

File tree

src/ImageSharp/Formats/Webp/AlphaEncoder.cs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,14 @@ internal static class AlphaEncoder
2727
/// <param name="size">The size in bytes of the alpha data.</param>
2828
/// <returns>The encoded alpha data.</returns>
2929
public static IMemoryOwner<byte> EncodeAlpha<TPixel>(
30-
ImageFrame<TPixel> frame,
30+
Buffer2DRegion<TPixel> frame,
3131
Configuration configuration,
3232
MemoryAllocator memoryAllocator,
3333
bool skipMetadata,
3434
bool compress,
3535
out int size)
3636
where TPixel : unmanaged, IPixel<TPixel>
3737
{
38-
int width = frame.Width;
39-
int height = frame.Height;
4038
IMemoryOwner<byte> alphaData = ExtractAlphaChannel(frame, configuration, memoryAllocator);
4139

4240
if (compress)
@@ -46,8 +44,8 @@ public static IMemoryOwner<byte> EncodeAlpha<TPixel>(
4644
using Vp8LEncoder lossLessEncoder = new(
4745
memoryAllocator,
4846
configuration,
49-
width,
50-
height,
47+
frame.Width,
48+
frame.Height,
5149
quality,
5250
skipMetadata,
5351
effort,
@@ -58,40 +56,43 @@ public static IMemoryOwner<byte> EncodeAlpha<TPixel>(
5856
// The transparency information will be stored in the green channel of the ARGB quadruplet.
5957
// The green channel is allowed extra transformation steps in the specification -- unlike the other channels,
6058
// that can improve compression.
61-
using ImageFrame<Rgba32> alphaAsFrame = DispatchAlphaToGreen(frame, alphaData.GetSpan());
59+
using ImageFrame<Bgra32> alphaAsFrame = DispatchAlphaToGreen(configuration, frame, alphaData.GetSpan());
6260

63-
size = lossLessEncoder.EncodeAlphaImageData(alphaAsFrame, alphaData);
61+
size = lossLessEncoder.EncodeAlphaImageData(alphaAsFrame.PixelBuffer.GetRegion(), alphaData);
6462

6563
return alphaData;
6664
}
6765

68-
size = width * height;
66+
size = frame.Width * frame.Height;
6967
return alphaData;
7068
}
7169

7270
/// <summary>
7371
/// Store the transparency in the green channel.
7472
/// </summary>
7573
/// <typeparam name="TPixel">The pixel format.</typeparam>
76-
/// <param name="frame">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
74+
/// <param name="configuration">The configuration.</param>
75+
/// <param name="frame">The pixel buffer to encode from.</param>
7776
/// <param name="alphaData">A byte sequence of length width * height, containing all the 8-bit transparency values in scan order.</param>
7877
/// <returns>The transparency frame.</returns>
79-
private static ImageFrame<Rgba32> DispatchAlphaToGreen<TPixel>(ImageFrame<TPixel> frame, Span<byte> alphaData)
78+
private static ImageFrame<Bgra32> DispatchAlphaToGreen<TPixel>(Configuration configuration, Buffer2DRegion<TPixel> frame, Span<byte> alphaData)
8079
where TPixel : unmanaged, IPixel<TPixel>
8180
{
8281
int width = frame.Width;
8382
int height = frame.Height;
84-
ImageFrame<Rgba32> alphaAsFrame = new ImageFrame<Rgba32>(Configuration.Default, width, height);
83+
ImageFrame<Bgra32> alphaAsFrame = new(configuration, width, height);
8584

8685
for (int y = 0; y < height; y++)
8786
{
88-
Memory<Rgba32> rowBuffer = alphaAsFrame.DangerousGetPixelRowMemory(y);
89-
Span<Rgba32> pixelRow = rowBuffer.Span;
87+
Memory<Bgra32> rowBuffer = alphaAsFrame.DangerousGetPixelRowMemory(y);
88+
Span<Bgra32> pixelRow = rowBuffer.Span;
9089
Span<byte> alphaRow = alphaData.Slice(y * width, width);
90+
91+
// TODO: This can be probably simd optimized.
9192
for (int x = 0; x < width; x++)
9293
{
9394
// Leave A/R/B channels zero'd.
94-
pixelRow[x] = new Rgba32(0, alphaRow[x], 0, 0);
95+
pixelRow[x] = new Bgra32(0, alphaRow[x], 0, 0);
9596
}
9697
}
9798

@@ -106,12 +107,12 @@ private static ImageFrame<Rgba32> DispatchAlphaToGreen<TPixel>(ImageFrame<TPixel
106107
/// <param name="configuration">The global configuration.</param>
107108
/// <param name="memoryAllocator">The memory manager.</param>
108109
/// <returns>A byte sequence of length width * height, containing all the 8-bit transparency values in scan order.</returns>
109-
private static IMemoryOwner<byte> ExtractAlphaChannel<TPixel>(ImageFrame<TPixel> frame, Configuration configuration, MemoryAllocator memoryAllocator)
110+
private static IMemoryOwner<byte> ExtractAlphaChannel<TPixel>(Buffer2DRegion<TPixel> frame, Configuration configuration, MemoryAllocator memoryAllocator)
110111
where TPixel : unmanaged, IPixel<TPixel>
111112
{
112-
Buffer2D<TPixel> imageBuffer = frame.PixelBuffer;
113-
int height = frame.Height;
114113
int width = frame.Width;
114+
int height = frame.Height;
115+
115116
IMemoryOwner<byte> alphaDataBuffer = memoryAllocator.Allocate<byte>(width * height);
116117
Span<byte> alphaData = alphaDataBuffer.GetSpan();
117118

@@ -120,7 +121,7 @@ private static IMemoryOwner<byte> ExtractAlphaChannel<TPixel>(ImageFrame<TPixel>
120121

121122
for (int y = 0; y < height; y++)
122123
{
123-
Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(y);
124+
Span<TPixel> rowSpan = frame.DangerousGetRowSpan(y);
124125
PixelOperations<TPixel>.Instance.ToRgba32(configuration, rowSpan, rgbaRow);
125126
int offset = y * width;
126127
for (int x = 0; x < width; x++)

src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4-
using System.Diagnostics;
54
using SixLabors.ImageSharp.Common.Helpers;
65
using SixLabors.ImageSharp.Formats.Webp.Chunks;
76
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@@ -100,9 +99,7 @@ public static void WriteTrunksBeforeData(
10099
bool hasAnimation)
101100
{
102101
// Write file size later
103-
long pos = RiffHelper.BeginWriteRiffFile(stream, WebpConstants.WebpFourCc);
104-
105-
Debug.Assert(pos is 4, "Stream should be written from position 0.");
102+
RiffHelper.BeginWriteRiffFile(stream, WebpConstants.WebpFourCc);
106103

107104
// Write VP8X, header if necessary.
108105
bool isVp8X = exifProfile != null || xmpProfile != null || iccProfile != null || hasAlpha || hasAnimation;

src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public WebpFrameData(uint x, uint y, uint width, uint height, uint duration, Web
8383
/// </summary>
8484
public WebpDisposalMethod DisposalMethod { get; }
8585

86-
public Rectangle Bounds => new((int)this.X * 2, (int)this.Y * 2, (int)this.Width, (int)this.Height);
86+
public Rectangle Bounds => new((int)this.X, (int)this.Y, (int)this.Width, (int)this.Height);
8787

8888
/// <summary>
8989
/// Writes the animation frame(<see cref="WebpChunkType.FrameData"/>) to the stream.
@@ -107,8 +107,8 @@ public long WriteHeaderTo(Stream stream)
107107

108108
long pos = RiffHelper.BeginWriteChunk(stream, (uint)WebpChunkType.FrameData);
109109

110-
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.X);
111-
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Y);
110+
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.X / 2);
111+
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Y / 2);
112112
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Width - 1);
113113
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Height - 1);
114114
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Duration);
@@ -128,8 +128,8 @@ public static WebpFrameData Parse(Stream stream)
128128

129129
WebpFrameData data = new(
130130
dataSize: WebpChunkParsingUtils.ReadChunkSize(stream, buffer),
131-
x: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
132-
y: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
131+
x: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) * 2,
132+
y: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) * 2,
133133
width: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,
134134
height: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,
135135
duration: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),

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

Lines changed: 29 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ public Vp8LEncoder(
240240
public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAnimation)
241241
where TPixel : unmanaged, IPixel<TPixel>
242242
{
243-
// Write bytes from the bitwriter buffer to the stream.
243+
// Write bytes from the bit-writer buffer to the stream.
244244
ImageMetadata metadata = image.Metadata;
245245
metadata.SyncProfiles();
246246

@@ -267,7 +267,7 @@ public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAni
267267
public void EncodeFooter<TPixel>(Image<TPixel> image, Stream stream)
268268
where TPixel : unmanaged, IPixel<TPixel>
269269
{
270-
// Write bytes from the bitwriter buffer to the stream.
270+
// Write bytes from the bit-writer buffer to the stream.
271271
ImageMetadata metadata = image.Metadata;
272272

273273
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
@@ -280,48 +280,44 @@ public void EncodeFooter<TPixel>(Image<TPixel> image, Stream stream)
280280
/// Encodes the image as lossless webp to the specified stream.
281281
/// </summary>
282282
/// <typeparam name="TPixel">The pixel format.</typeparam>
283-
/// <param name="frame">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
283+
/// <param name="frame">The image frame to encode from.</param>
284+
/// <param name="bounds">The region of interest within the frame to encode.</param>
285+
/// <param name="frameMetadata">The frame metadata.</param>
284286
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
285287
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
286-
public void Encode<TPixel>(ImageFrame<TPixel> frame, Stream stream, bool hasAnimation)
288+
public void Encode<TPixel>(ImageFrame<TPixel> frame, Rectangle bounds, WebpFrameMetadata frameMetadata, Stream stream, bool hasAnimation)
287289
where TPixel : unmanaged, IPixel<TPixel>
288290
{
289-
int width = frame.Width;
290-
int height = frame.Height;
291-
292291
// Convert image pixels to bgra array.
293-
bool hasAlpha = this.ConvertPixelsToBgra(frame, width, height);
292+
bool hasAlpha = this.ConvertPixelsToBgra(frame.PixelBuffer.GetRegion(bounds));
294293

295294
// Write the image size.
296-
this.WriteImageSize(width, height);
295+
this.WriteImageSize(bounds.Width, bounds.Height);
297296

298297
// Write the non-trivial Alpha flag and lossless version.
299298
this.WriteAlphaAndVersion(hasAlpha);
300299

301300
// Encode the main image stream.
302-
this.EncodeStream(frame);
301+
this.EncodeStream(bounds.Width, bounds.Height);
303302

304303
this.bitWriter.Finish();
305304

306305
long prevPosition = 0;
307306

308307
if (hasAnimation)
309308
{
310-
WebpFrameMetadata frameMetadata = WebpCommonUtils.GetWebpFrameMetadata(frame);
311-
312-
// TODO: If we can clip the indexed frame for transparent bounds we can set properties here.
313309
prevPosition = new WebpFrameData(
314-
0,
315-
0,
316-
(uint)frame.Width,
317-
(uint)frame.Height,
310+
(uint)bounds.Left,
311+
(uint)bounds.Top,
312+
(uint)bounds.Width,
313+
(uint)bounds.Height,
318314
frameMetadata.FrameDelay,
319315
frameMetadata.BlendMethod,
320316
frameMetadata.DisposalMethod)
321317
.WriteHeaderTo(stream);
322318
}
323319

324-
// Write bytes from the bitwriter buffer to the stream.
320+
// Write bytes from the bit-writer buffer to the stream.
325321
this.bitWriter.WriteEncodedImageToStream(stream);
326322

327323
if (hasAnimation)
@@ -334,23 +330,23 @@ public void Encode<TPixel>(ImageFrame<TPixel> frame, Stream stream, bool hasAnim
334330
/// Encodes the alpha image data using the webp lossless compression.
335331
/// </summary>
336332
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
337-
/// <param name="frame">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
333+
/// <param name="frame">The alpha-pixel data to encode from.</param>
338334
/// <param name="alphaData">The destination buffer to write the encoded alpha data to.</param>
339335
/// <returns>The size of the compressed data in bytes.
340336
/// If the size of the data is the same as the pixel count, the compression would not yield in smaller data and is left uncompressed.
341337
/// </returns>
342-
public int EncodeAlphaImageData<TPixel>(ImageFrame<TPixel> frame, IMemoryOwner<byte> alphaData)
338+
public int EncodeAlphaImageData<TPixel>(Buffer2DRegion<TPixel> frame, IMemoryOwner<byte> alphaData)
343339
where TPixel : unmanaged, IPixel<TPixel>
344340
{
345341
int width = frame.Width;
346342
int height = frame.Height;
347343
int pixelCount = width * height;
348344

349345
// Convert image pixels to bgra array.
350-
this.ConvertPixelsToBgra(frame, width, height);
346+
this.ConvertPixelsToBgra(frame);
351347

352348
// The image-stream will NOT contain any headers describing the image dimension, the dimension is already known.
353-
this.EncodeStream(frame);
349+
this.EncodeStream(width, height);
354350
this.bitWriter.Finish();
355351
int size = this.bitWriter.NumBytes;
356352
if (size >= pixelCount)
@@ -364,7 +360,7 @@ public int EncodeAlphaImageData<TPixel>(ImageFrame<TPixel> frame, IMemoryOwner<b
364360
}
365361

366362
/// <summary>
367-
/// Writes the image size to the bitwriter buffer.
363+
/// Writes the image size to the bit writer buffer.
368364
/// </summary>
369365
/// <param name="inputImgWidth">The input image width.</param>
370366
/// <param name="inputImgHeight">The input image height.</param>
@@ -381,7 +377,7 @@ private void WriteImageSize(int inputImgWidth, int inputImgHeight)
381377
}
382378

383379
/// <summary>
384-
/// Writes a flag indicating if alpha channel is used and the VP8L version to the bitwriter buffer.
380+
/// Writes a flag indicating if alpha channel is used and the VP8L version to the bit-writer buffer.
385381
/// </summary>
386382
/// <param name="hasAlpha">Indicates if a alpha channel is present.</param>
387383
private void WriteAlphaAndVersion(bool hasAlpha)
@@ -393,14 +389,10 @@ private void WriteAlphaAndVersion(bool hasAlpha)
393389
/// <summary>
394390
/// Encodes the image stream using lossless webp format.
395391
/// </summary>
396-
/// <typeparam name="TPixel">The pixel type.</typeparam>
397-
/// <param name="frame">The frame to encode.</param>
398-
private void EncodeStream<TPixel>(ImageFrame<TPixel> frame)
399-
where TPixel : unmanaged, IPixel<TPixel>
392+
/// <param name="width">The image frame width.</param>
393+
/// <param name="height">The image frame height.</param>
394+
private void EncodeStream(int width, int height)
400395
{
401-
int width = frame.Width;
402-
int height = frame.Height;
403-
404396
Span<uint> bgra = this.Bgra.GetSpan();
405397
Span<uint> encodedData = this.EncodedData.GetSpan();
406398
bool lowEffort = this.method == 0;
@@ -508,23 +500,20 @@ private void EncodeStream<TPixel>(ImageFrame<TPixel> frame)
508500
/// Converts the pixels of the image to bgra.
509501
/// </summary>
510502
/// <typeparam name="TPixel">The type of the pixels.</typeparam>
511-
/// <param name="frame">The frame to convert.</param>
512-
/// <param name="width">The width of the image.</param>
513-
/// <param name="height">The height of the image.</param>
503+
/// <param name="pixels">The frame pixel buffer to convert.</param>
514504
/// <returns>true, if the image is non opaque.</returns>
515-
private bool ConvertPixelsToBgra<TPixel>(ImageFrame<TPixel> frame, int width, int height)
505+
private bool ConvertPixelsToBgra<TPixel>(Buffer2DRegion<TPixel> pixels)
516506
where TPixel : unmanaged, IPixel<TPixel>
517507
{
518-
Buffer2D<TPixel> imageBuffer = frame.PixelBuffer;
519508
bool nonOpaque = false;
520509
Span<uint> bgra = this.Bgra.GetSpan();
521510
Span<byte> bgraBytes = MemoryMarshal.Cast<uint, byte>(bgra);
522-
int widthBytes = width * 4;
523-
for (int y = 0; y < height; y++)
511+
int widthBytes = pixels.Width * 4;
512+
for (int y = 0; y < pixels.Height; y++)
524513
{
525-
Span<TPixel> rowSpan = imageBuffer.DangerousGetRowSpan(y);
514+
Span<TPixel> rowSpan = pixels.DangerousGetRowSpan(y);
526515
Span<byte> rowBytes = bgraBytes.Slice(y * widthBytes, widthBytes);
527-
PixelOperations<TPixel>.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, width);
516+
PixelOperations<TPixel>.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, pixels.Width);
528517
if (!nonOpaque)
529518
{
530519
Span<Bgra32> rowBgra = MemoryMarshal.Cast<byte, Bgra32>(rowBytes);

0 commit comments

Comments
 (0)