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.