diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs
new file mode 100644
index 0000000000..da026b1999
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs
@@ -0,0 +1,294 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.WebP.Lossy
+{
+ ///
+ /// Iterator structure to iterate through macroblocks, pointing to the
+ /// right neighbouring data (samples, predictions, contexts, ...)
+ ///
+ internal class Vp8EncIterator : IDisposable
+ {
+ private const int YOffEnc = 0;
+
+ private const int UOffEnc = 16;
+
+ private const int VOffEnc = 16 + 8;
+
+ private readonly int mbw;
+
+ private readonly int mbh;
+
+ public Vp8EncIterator(MemoryAllocator memoryAllocator, IMemoryOwner yTop, IMemoryOwner uvTop, int mbw, int mbh)
+ {
+ this.mbw = mbw;
+ this.mbh = mbh;
+ this.YTop = yTop;
+ this.UvTop = uvTop;
+ this.YuvIn = memoryAllocator.Allocate(WebPConstants.Bps * 16);
+ this.YuvOut = memoryAllocator.Allocate(WebPConstants.Bps * 16);
+ this.YuvOut2 = memoryAllocator.Allocate(WebPConstants.Bps * 16);
+ this.YLeft = memoryAllocator.Allocate(WebPConstants.Bps + 1);
+ this.ULeft = memoryAllocator.Allocate(16);
+ this.VLeft = memoryAllocator.Allocate(16);
+ this.TopNz = memoryAllocator.Allocate(9);
+ this.LeftNz = memoryAllocator.Allocate(9);
+
+ this.Reset();
+ }
+
+ ///
+ /// Gets or sets the current macroblock X value.
+ ///
+ public int X { get; set; }
+
+ ///
+ /// Gets or sets the current macroblock Y.
+ ///
+ public int Y { get; set; }
+
+ ///
+ /// Gets or sets the input samples.
+ ///
+ public IMemoryOwner YuvIn { get; set; }
+
+ ///
+ /// Gets or sets the output samples.
+ ///
+ public IMemoryOwner YuvOut { get; set; }
+
+ public IMemoryOwner YuvOut2 { get; set; }
+
+ ///
+ /// Gets or sets the left luma samples.
+ ///
+ public IMemoryOwner YLeft { get; set; }
+
+ ///
+ /// Gets or sets the left u samples.
+ ///
+ public IMemoryOwner ULeft { get; set; }
+
+ ///
+ /// Gets or sets the left v samples.
+ ///
+ public IMemoryOwner VLeft { get; set; }
+
+ ///
+ /// Gets or sets the top luma samples at position 'X'.
+ ///
+ public IMemoryOwner YTop { get; set; }
+
+ ///
+ /// Gets or sets the top u/v samples at position 'X', packed as 16 bytes.
+ ///
+ public IMemoryOwner UvTop { get; set; }
+
+ ///
+ /// Gets or sets the non-zero pattern.
+ ///
+ public IMemoryOwner Nz { get; set; }
+
+ ///
+ /// Gets or sets the top-non-zero context.
+ ///
+ public IMemoryOwner TopNz { get; set; }
+
+ ///
+ /// Gets or sets the left-non-zero. leftNz[8] is independent.
+ ///
+ public IMemoryOwner LeftNz { get; set; }
+
+ ///
+ /// Gets or sets the number of mb still to be processed.
+ ///
+ public int CountDown { get; set; }
+
+ public void Import(Span y, Span u, Span v, int yStride, int uvStride, int width, int height)
+ {
+ int yStartIdx = ((this.Y * yStride) + this.X) * 16;
+ int uvStartIdx = ((this.Y * uvStride) + this.X) * 8;
+ Span ySrc = y.Slice(yStartIdx);
+ Span uSrc = u.Slice(uvStartIdx);
+ Span vSrc = v.Slice(uvStartIdx);
+ int w = Math.Min(width - (this.X * 16), 16);
+ int h = Math.Min(height - (this.Y * 16), 16);
+ int uvw = (w + 1) >> 1;
+ int uvh = (h + 1) >> 1;
+
+ Span yuvIn = this.YuvIn.Slice(YOffEnc);
+ Span uIn = this.YuvIn.Slice(UOffEnc);
+ Span vIn = this.YuvIn.Slice(VOffEnc);
+ this.ImportBlock(ySrc, yStride, yuvIn, w, h, 16);
+ this.ImportBlock(uSrc, uvStride, uIn, uvw, uvh, 8);
+ this.ImportBlock(vSrc, uvStride, vIn, uvw, uvh, 8);
+
+ // Import source (uncompressed) samples into boundary.
+ if (this.X == 0)
+ {
+ this.InitLeft();
+ }
+ else
+ {
+ Span yLeft = this.YLeft.GetSpan();
+ Span uLeft = this.ULeft.GetSpan();
+ Span vLeft = this.VLeft.GetSpan();
+ if (this.Y == 0)
+ {
+ yLeft[0] = 127;
+ uLeft[0] = 127;
+ vLeft[0] = 127;
+ }
+ else
+ {
+ yLeft[0] = ySrc[-1 - yStride];
+ uLeft[0] = uSrc[-1 - uvStride];
+ vLeft[0] = vSrc[-1 - uvStride];
+ }
+
+ this.ImportLine(y.Slice(yStartIdx - 1), yStride, yLeft.Slice(1), h, 16);
+ this.ImportLine(u.Slice(uvStartIdx - 1), uvStride, uLeft.Slice(1), uvh, 8);
+ this.ImportLine(v.Slice(uvStartIdx - 1), uvStride, vLeft.Slice(1), uvh, 8);
+ }
+
+ if (this.Y == 0)
+ {
+ this.YTop.GetSpan().Fill(127);
+ this.UvTop.GetSpan().Fill(127);
+ }
+ else
+ {
+ this.ImportLine(y.Slice(yStartIdx - yStride), 1, this.YTop.GetSpan(), w, 16);
+ this.ImportLine(u.Slice(uvStartIdx - uvStride), 1, this.UvTop.GetSpan(), uvw, 8);
+ this.ImportLine(v.Slice(uvStartIdx - uvStride), 1, this.UvTop.GetSpan().Slice(8), uvw, 8);
+ }
+ }
+
+ public bool IsDone()
+ {
+ return this.CountDown <= 0;
+ }
+
+ ///
+ public void Dispose()
+ {
+ this.YuvIn.Dispose();
+ this.YuvOut.Dispose();
+ this.YuvOut2.Dispose();
+ this.YLeft.Dispose();
+ this.ULeft.Dispose();
+ this.VLeft.Dispose();
+ this.Nz.Dispose();
+ this.LeftNz.Dispose();
+ this.TopNz.Dispose();
+ }
+
+ public bool Next(int mbw)
+ {
+ if (++this.X == mbw)
+ {
+ this.SetRow(++this.Y);
+ }
+ else
+ {
+ // TODO:
+ /* it->preds_ += 4;
+ it->mb_ += 1;
+ it->nz_ += 1;
+ it->y_top_ += 16;
+ it->uv_top_ += 16;*/
+ }
+
+ return --this.CountDown > 0;
+ }
+
+ private void ImportBlock(Span src, int srcStride, Span dst, int w, int h, int size)
+ {
+ int dstIdx = 0;
+ for (int i = 0; i < h; ++i)
+ {
+ src.Slice(0, w).CopyTo(dst.Slice(dstIdx));
+ if (w < size)
+ {
+ dst.Slice(dstIdx, size - w).Fill(dst[dstIdx + w - 1]);
+ }
+
+ dstIdx += WebPConstants.Bps;
+ src = src.Slice(srcStride);
+ }
+
+ for (int i = h; i < size; ++i)
+ {
+ dst.Slice(dstIdx - WebPConstants.Bps, size).CopyTo(dst);
+ dstIdx += WebPConstants.Bps;
+ }
+ }
+
+ private void ImportLine(Span src, int srcStride, Span dst, int len, int totalLen)
+ {
+ int i;
+ for (i = 0; i < len; ++i)
+ {
+ dst[i] = src[i];
+ src = src.Slice(srcStride);
+ }
+
+ for (; i < totalLen; ++i)
+ {
+ dst[i] = dst[len - 1];
+ }
+ }
+
+ private void Reset()
+ {
+ this.SetRow(0);
+ this.SetCountDown(this.mbw * this.mbh);
+ this.InitTop();
+ // TODO: memset(it->bit_count_, 0, sizeof(it->bit_count_));
+ }
+
+ private void SetRow(int y)
+ {
+ this.X = 0;
+ this.Y = y;
+
+ // TODO:
+ // it->preds_ = enc->preds_ + y * 4 * enc->preds_w_;
+ // it->nz_ = enc->nz_;
+ // it->mb_ = enc->mb_info_ + y * enc->mb_w_;
+ // it->y_top_ = enc->y_top_;
+ // it->uv_top_ = enc->uv_top_;
+ }
+
+ private void InitLeft()
+ {
+ Span yLeft = this.YLeft.GetSpan();
+ Span uLeft = this.ULeft.GetSpan();
+ Span vLeft = this.VLeft.GetSpan();
+ byte val = (byte)((this.Y > 0) ? 129 : 127);
+ yLeft[0] = val;
+ uLeft[0] = val;
+ vLeft[0] = val;
+ this.YLeft.Slice(1).Fill(129);
+ this.ULeft.Slice(1).Fill(129);
+ this.VLeft.Slice(1).Fill(129);
+ this.LeftNz.GetSpan()[8] = 0;
+ }
+
+ private void InitTop()
+ {
+ int topSize = this.mbw * 16;
+ this.YTop.Slice(0, topSize).Fill(127);
+ // TODO: memset(enc->nz_, 0, enc->mb_w_ * sizeof(*enc->nz_));
+ }
+
+ private void SetCountDown(int countDown)
+ {
+ this.CountDown = countDown;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
index d29a5cf7a6..1fddabaa06 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
@@ -45,10 +45,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.memoryAllocator = memoryAllocator;
var pixelCount = width * height;
+ int mbw = (width + 15) >> 4;
var uvSize = ((width + 1) >> 1) * ((height + 1) >> 1);
this.Y = this.memoryAllocator.Allocate(pixelCount);
this.U = this.memoryAllocator.Allocate(uvSize);
this.V = this.memoryAllocator.Allocate(uvSize);
+ this.YTop = this.memoryAllocator.Allocate(mbw * 16);
+ this.UvTop = this.memoryAllocator.Allocate(mbw * 16 * 2);
// TODO: properly initialize the bitwriter
this.bitWriter = new Vp8BitWriter();
@@ -60,8 +63,54 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
private IMemoryOwner V { get; }
+ ///
+ /// Gets the top luma samples.
+ ///
+ private IMemoryOwner YTop { get; }
+
+ ///
+ /// Gets the top u/v samples. U and V are packed into 16 bytes (8 U + 8 V).
+ ///
+ private IMemoryOwner UvTop { get; }
+
public void Encode(Image image, Stream stream)
where TPixel : unmanaged, IPixel
+ {
+ this.ConvertRgbToYuv(image);
+ Span y = this.Y.GetSpan();
+ Span u = this.U.GetSpan();
+ Span v = this.V.GetSpan();
+
+ int mbw = (image.Width + 15) >> 4;
+ int mbh = (image.Height + 15) >> 4;
+ int yStride = image.Width;
+ int uvStride = (yStride + 1) >> 1;
+ var it = new Vp8EncIterator(this.memoryAllocator, this.YTop, this.UvTop, mbw, mbh);
+ if (!it.IsDone())
+ {
+ do
+ {
+ it.Import(y, u, v, yStride, uvStride, image.Width, image.Height);
+ // TODO: MBAnalyze
+ }
+ while (it.Next(mbw));
+ }
+
+ throw new NotImplementedException();
+ }
+
+ ///
+ public void Dispose()
+ {
+ this.Y.Dispose();
+ this.U.Dispose();
+ this.V.Dispose();
+ this.YTop.Dispose();
+ this.UvTop.Dispose();
+ }
+
+ private void ConvertRgbToYuv(Image image)
+ where TPixel : unmanaged, IPixel
{
int uvWidth = (image.Width + 1) >> 1;
bool hasAlpha = this.CheckNonOpaque(image);
@@ -109,16 +158,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.ConvertRgbaToY(rowSpan, this.Y.Slice(rowIndex * image.Width), image.Width);
}
}
-
- throw new NotImplementedException();
- }
-
- ///
- public void Dispose()
- {
- this.Y.Dispose();
- this.U.Dispose();
- this.V.Dispose();
}
// Returns true if alpha has non-0xff values.