Browse Source

Write encoded image data to the stream, fix some indexing issues

pull/1552/head
Brian Popow 6 years ago
parent
commit
adc2efc650
  1. 77
      src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs
  2. 8
      src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs
  3. 19
      src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs
  4. 32
      src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
  5. 136
      src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs
  6. 22
      src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
  7. 4
      src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs
  8. 2
      src/ImageSharp/Formats/WebP/WebPConstants.cs

77
src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.IO;
namespace SixLabors.ImageSharp.Formats.WebP.BitWriter
{
@ -33,12 +35,32 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter
get { return this.buffer; }
}
/// <summary>
/// Writes the encoded bytes of the image to the stream. Call Finish() before this.
/// </summary>
/// <param name="stream">The stream to write to.</param>
public void WriteToStream(Stream stream)
{
stream.Write(this.Buffer.AsSpan(0, this.NumBytes()));
}
/// <summary>
/// Resizes the buffer to write to.
/// </summary>
/// <param name="extraSize">The extra size in bytes needed.</param>
public abstract void BitWriterResize(int extraSize);
/// <summary>
/// Returns the number of bytes of the encoded image data.
/// </summary>
/// <returns>The number of bytes of the image data.</returns>
public abstract int NumBytes();
/// <summary>
/// Flush leftover bits.
/// </summary>
public abstract void Finish();
protected bool ResizeBuffer(int maxBytes, int sizeRequired)
{
if (maxBytes > 0 && sizeRequired < maxBytes)
@ -60,5 +82,60 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter
return false;
}
/// <summary>
/// Writes the encoded image to the stream.
/// </summary>
/// <param name="lossy">If true, lossy tag will be written, otherwise a lossless tag.</param>
/// <param name="stream">The stream to write to.</param>
public void WriteEncodedImageToStream(bool lossy, Stream stream)
{
this.Finish();
var numBytes = this.NumBytes();
var size = numBytes;
if (!lossy)
{
size++; // One byte extra for the VP8L signature.
}
var pad = size & 1;
var riffSize = WebPConstants.TagSize + WebPConstants.ChunkHeaderSize + size + pad;
this.WriteRiffHeader(riffSize, size, lossy, stream);
this.WriteToStream(stream);
if (pad == 1)
{
stream.WriteByte(0);
}
}
/// <summary>
/// Writes the RIFF header to the stream.
/// </summary>
/// <param name="riffSize">The block length.</param>
/// <param name="size">The size in bytes of the encoded image.</param>
/// <param name="lossy">If true, lossy tag will be written, otherwise a lossless tag.</param>
/// <param name="stream">The stream to write to.</param>
private void WriteRiffHeader(int riffSize, int size, bool lossy, Stream stream)
{
Span<byte> buffer = stackalloc byte[4];
stream.Write(WebPConstants.RiffFourCc);
BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)riffSize);
stream.Write(buffer);
stream.Write(WebPConstants.WebPHeader);
if (lossy)
{
stream.Write(WebPConstants.Vp8MagicBytes);
}
else
{
stream.Write(WebPConstants.Vp8LMagicBytes);
}
BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)size);
stream.Write(buffer);
stream.WriteByte(WebPConstants.Vp8LMagicByte);
}
}
}

8
src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs

@ -47,9 +47,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter
// this.error = false;
}
public uint Pos
/// <inheritdoc/>
public override int NumBytes()
{
get { return this.pos; }
return (int)this.pos;
}
public int PutCoeffs(int ctx, Vp8Residual residual)
@ -179,7 +180,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter
this.ResizeBuffer(this.maxPos, (int)neededSize);
}
public void Finish()
/// <inheritdoc/>
public override void Finish()
{
this.PutBits(0, 9 - this.nbBits);
this.nbBits = 0; // pad with zeroes.

19
src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs

@ -3,7 +3,6 @@
using System;
using System.Buffers.Binary;
using System.IO;
using SixLabors.ImageSharp.Formats.WebP.Lossless;
namespace SixLabors.ImageSharp.Formats.WebP.BitWriter
@ -100,7 +99,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter
this.PutBits((uint)((bits << depth) | symbol), depth + nBits);
}
public int NumBytes()
/// <inheritdoc/>
public override int NumBytes()
{
return this.cur + ((this.used + 7) >> 3);
}
@ -112,19 +112,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter
return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur);
}
/// <summary>
/// Writes the encoded bytes of the image to the stream. Call BitWriterFinish() before this.
/// </summary>
/// <param name="stream">The stream to write to.</param>
public void WriteToStream(Stream stream)
{
stream.Write(this.Buffer.AsSpan(0, this.NumBytes()));
}
/// <summary>
/// Flush leftover bits.
/// </summary>
public void BitWriterFinish()
/// <inheritdoc/>
public override void Finish()
{
this.BitWriterResize((this.used + 7) >> 3);
while (this.used > 0)

32
src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs

@ -186,17 +186,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
this.EncodeStream(image);
// Write bytes from the bitwriter buffer to the stream.
this.bitWriter.BitWriterFinish();
var numBytes = this.bitWriter.NumBytes();
var vp8LSize = 1 + numBytes; // One byte extra for the VP8L signature.
var pad = vp8LSize & 1;
var riffSize = WebPConstants.TagSize + WebPConstants.ChunkHeaderSize + vp8LSize + pad;
this.WriteRiffHeader(riffSize, vp8LSize, stream);
this.bitWriter.WriteToStream(stream);
if (pad == 1)
{
stream.WriteByte(0);
}
this.bitWriter.WriteEncodedImageToStream(lossy: false, stream);
}
/// <summary>
@ -226,26 +216,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
this.bitWriter.PutBits(WebPConstants.Vp8LVersion, WebPConstants.Vp8LVersionBits);
}
/// <summary>
/// Writes the RIFF header to the stream.
/// </summary>
/// <param name="riffSize">The block length.</param>
/// <param name="vp8LSize">The size in bytes of the compressed image.</param>
/// <param name="stream">The stream to write to.</param>
private void WriteRiffHeader(int riffSize, int vp8LSize, Stream stream)
{
Span<byte> buffer = stackalloc byte[4];
stream.Write(WebPConstants.RiffFourCc);
BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)riffSize);
stream.Write(buffer);
stream.Write(WebPConstants.WebPHeader);
stream.Write(WebPConstants.Vp8LTag);
BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)vp8LSize);
stream.Write(buffer);
stream.WriteByte(WebPConstants.Vp8LMagicByte);
}
/// <summary>
/// Encodes the image stream using lossless webp format.
/// </summary>

136
src/ImageSharp/Formats/WebP/Lossy/Vp8EncIterator.cs

@ -376,7 +376,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
else
{
this.ImportLine(y.Slice(yStartIdx - yStride), 1, yTop, w, 16);
this.ImportLine(u.Slice(uvStartIdx - uvStride), 1, this.UvTop.GetSpan(), uvw, 8);
this.ImportLine(u.Slice(uvStartIdx - uvStride), 1, this.UvTop.GetSpan().Slice(8), uvw, 8);
this.ImportLine(v.Slice(uvStartIdx - uvStride), 1, this.UvTop.GetSpan().Slice(8), uvw, 8);
}
}
@ -502,8 +502,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
int predIdx = this.predIdx;
int x = this.I4 & 3;
int y = this.I4 >> 2;
int left = (x == 0) ? this.Preds.Slice(predIdx)[(y * predsWidth) - 1] : modes[this.I4 - 1];
int top = (y == 0) ? this.Preds.Slice(predIdx)[-predsWidth + x] : modes[this.I4 - 4];
int left = (x == 0) ? this.Preds.Slice(predIdx + (y * predsWidth) - 1)[0] : modes[this.I4 - 1];
int top = (y == 0) ? this.Preds.Slice(predIdx - predsWidth + x)[0] : modes[this.I4 - 4];
return WebPLookupTables.Vp8FixedCostsI4[top, left];
}
@ -590,13 +590,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
public bool RotateI4(Span<byte> yuvOut)
{
Span<byte> blk = yuvOut.Slice(WebPLookupTables.Vp8Scan[this.I4]);
Span<byte> top = this.I4Boundary.AsSpan(this.I4BoundaryIdx);
Span<byte> top = this.I4Boundary.AsSpan();
int topOffset = this.I4BoundaryIdx;
int i;
// Update the cache with 7 fresh samples.
for (i = 0; i <= 3; ++i)
{
top[-4 + i] = blk[i + (3 * WebPConstants.Bps)]; // Store future top samples.
top[topOffset - 4 + i] = blk[i + (3 * WebPConstants.Bps)]; // Store future top samples.
}
if ((this.I4 & 3) != 3)
@ -605,7 +606,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
for (i = 0; i <= 2; ++i)
{
// store future left samples
top[i] = blk[3 + ((2 - i) * WebPConstants.Bps)];
top[topOffset + i] = blk[3 + ((2 - i) * WebPConstants.Bps)];
}
}
else
@ -613,7 +614,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
// else replicate top-right samples, as says the specs.
for (i = 0; i <= 3; ++i)
{
top[i] = top[i + 4];
top[topOffset + i] = top[topOffset + i + 4];
}
}
@ -660,7 +661,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
public void MakeIntra4Preds()
{
this.EncPredLuma4(this.YuvP, this.I4Boundary.AsSpan(this.I4BoundaryIdx));
this.EncPredLuma4(this.YuvP, this.I4Boundary, this.I4BoundaryIdx);
}
public void SwapOut()
@ -788,18 +789,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
// Left samples are top[-5 .. -2], top_left is top[-1], top are
// located at top[0..3], and top right is top[4..7]
private void EncPredLuma4(Span<byte> dst, Span<byte> top)
private void EncPredLuma4(Span<byte> dst, Span<byte> top, int topOffset)
{
this.Dc4(dst.Slice(I4DC4), top);
this.Tm4(dst.Slice(I4TM4), top);
this.Ve4(dst.Slice(I4VE4), top);
this.He4(dst.Slice(I4HE4), top);
this.Rd4(dst.Slice(I4RD4), top);
this.Vr4(dst.Slice(I4VR4), top);
this.Dc4(dst, top, topOffset);
this.Tm4(dst.Slice(I4TM4), top, topOffset);
this.Ve4(dst.Slice(I4VE4), top, topOffset);
this.He4(dst.Slice(I4HE4), top, topOffset);
this.Rd4(dst.Slice(I4RD4), top, topOffset);
this.Vr4(dst.Slice(I4VR4), top, topOffset);
this.Ld4(dst.Slice(I4LD4), top);
this.Vl4(dst.Slice(I4VL4), top);
this.Hd4(dst.Slice(I4HD4), top);
this.Hu4(dst.Slice(I4HU4), top);
this.Hd4(dst.Slice(I4HD4), top, topOffset);
this.Hu4(dst.Slice(I4HU4), top, topOffset);
}
private void DcMode(Span<byte> dst, Span<byte> left, Span<byte> top, int size, int round, int shift)
@ -922,42 +923,42 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
}
private void Dc4(Span<byte> dst, Span<byte> top)
private void Dc4(Span<byte> dst, Span<byte> top, int topOffset)
{
uint dc = 4;
int i;
for (i = 0; i < 4; ++i)
{
dc += (uint)(top[i] + top[-5 + i]);
dc += (uint)(top[topOffset + i] + top[topOffset - 5 + i]);
}
this.Fill(dst, (int)(dc >> 3), 4);
}
private void Tm4(Span<byte> dst, Span<byte> top)
private void Tm4(Span<byte> dst, Span<byte> top, int topOffset)
{
Span<byte> clip = this.clip1.AsSpan(255 - top[-1]);
Span<byte> clip = this.clip1.AsSpan(255 - top[topOffset - 1]);
for (int y = 0; y < 4; ++y)
{
Span<byte> clipTable = clip.Slice(top[-2 - y]);
Span<byte> clipTable = clip.Slice(top[topOffset - 2 - y]);
for (int x = 0; x < 4; ++x)
{
dst[x] = clipTable[top[x]];
dst[x] = clipTable[top[topOffset + x]];
}
dst = dst.Slice(WebPConstants.Bps);
}
}
private void Ve4(Span<byte> dst, Span<byte> top)
private void Ve4(Span<byte> dst, Span<byte> top, int topOffset)
{
// vertical
byte[] vals =
{
LossyUtils.Avg3(top[-1], top[0], top[1]),
LossyUtils.Avg3(top[0], top[1], top[2]),
LossyUtils.Avg3(top[1], top[2], top[3]),
LossyUtils.Avg3(top[2], top[3], top[4])
LossyUtils.Avg3(top[topOffset - 1], top[topOffset], top[topOffset + 1]),
LossyUtils.Avg3(top[topOffset], top[topOffset + 1], top[topOffset + 2]),
LossyUtils.Avg3(top[topOffset + 1], top[topOffset + 2], top[topOffset + 3]),
LossyUtils.Avg3(top[topOffset + 2], top[topOffset + 3], top[topOffset + 4])
};
for (int i = 0; i < 4; ++i)
@ -966,14 +967,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
}
private void He4(Span<byte> dst, Span<byte> top)
private void He4(Span<byte> dst, Span<byte> top, int topOffset)
{
// horizontal
byte x = top[-1];
byte i = top[-2];
byte j = top[-3];
byte k = top[-4];
byte l = top[-5];
byte x = top[topOffset - 1];
byte i = top[topOffset - 2];
byte j = top[topOffset - 3];
byte k = top[topOffset - 4];
byte l = top[topOffset - 5];
uint val = 0x01010101U * LossyUtils.Avg3(x, i, j);
BinaryPrimitives.WriteUInt32BigEndian(dst, val);
@ -985,17 +986,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(1 * WebPConstants.Bps), val);
}
private void Rd4(Span<byte> dst, Span<byte> top)
private void Rd4(Span<byte> dst, Span<byte> top, int topOffset)
{
byte x = top[-1];
byte i = top[-2];
byte j = top[-3];
byte k = top[-4];
byte l = top[-5];
byte a = top[0];
byte b = top[1];
byte c = top[2];
byte d = top[3];
byte x = top[topOffset - 1];
byte i = top[topOffset - 2];
byte j = top[topOffset - 3];
byte k = top[topOffset - 4];
byte l = top[topOffset - 5];
byte a = top[topOffset];
byte b = top[topOffset + 1];
byte c = top[topOffset + 2];
byte d = top[topOffset + 3];
LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(j, k, l));
var ijk = LossyUtils.Avg3(i, j, k);
@ -1020,16 +1021,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(d, c, b));
}
private void Vr4(Span<byte> dst, Span<byte> top)
private void Vr4(Span<byte> dst, Span<byte> top, int topOffset)
{
byte x = top[-1];
byte i = top[-2];
byte j = top[-3];
byte k = top[-4];
byte a = top[0];
byte b = top[1];
byte c = top[2];
byte d = top[3];
byte x = top[topOffset - 1];
byte i = top[topOffset - 2];
byte j = top[topOffset - 3];
byte k = top[topOffset - 4];
byte a = top[topOffset];
byte b = top[topOffset + 1];
byte c = top[topOffset + 2];
byte d = top[topOffset + 3];
var xa = LossyUtils.Avg2(x, a);
LossyUtils.Dst(dst, 0, 0, xa);
@ -1124,16 +1125,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(f, g, h));
}
private void Hd4(Span<byte> dst, Span<byte> top)
private void Hd4(Span<byte> dst, Span<byte> top, int topOffset)
{
byte x = top[-1];
byte i = top[-2];
byte j = top[-3];
byte k = top[-4];
byte l = top[-5];
byte a = top[0];
byte b = top[1];
byte c = top[2];
byte x = top[topOffset - 1];
byte i = top[topOffset - 2];
byte j = top[topOffset - 3];
byte k = top[topOffset - 4];
byte l = top[topOffset - 5];
byte a = top[topOffset];
byte b = top[topOffset + 1];
byte c = top[topOffset + 2];
var ix = LossyUtils.Avg2(i, x);
LossyUtils.Dst(dst, 0, 0, ix);
@ -1159,12 +1160,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
LossyUtils.Dst(dst, 1, 3, LossyUtils.Avg3(l, k, j));
}
private void Hu4(Span<byte> dst, Span<byte> top)
private void Hu4(Span<byte> dst, Span<byte> top, int topOffset)
{
byte i = top[-2];
byte j = top[-3];
byte k = top[-4];
byte l = top[-5];
byte i = top[topOffset - 2];
byte j = top[topOffset - 3];
byte k = top[topOffset - 4];
byte l = top[topOffset - 5];
LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(i, j));
var jk = LossyUtils.Avg2(j, k);
@ -1285,6 +1286,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
{
int topSize = this.mbw * 16;
this.YTop.Slice(0, topSize).Fill(127);
this.UvTop.GetSpan().Fill(127);
this.Nz.GetSpan().Fill(0);
}

22
src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices;
@ -250,7 +251,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
while (it.Next());
this.bitWriter.Finish();
// Write bytes from the bitwriter buffer to the stream.
this.bitWriter.WriteEncodedImageToStream(lossy: true, stream);
}
/// <inheritdoc/>
@ -708,7 +710,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
it.NzToBytes();
var pos1 = this.bitWriter.Pos;
int pos1 = this.bitWriter.NumBytes();
if (i16)
{
residual.Init(0, 1, this.proba);
@ -734,7 +736,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
}
var pos2 = this.bitWriter.Pos;
int pos2 = this.bitWriter.NumBytes();
// U/V
residual.Init(0, 2, this.proba);
@ -752,7 +754,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
}
}
var pos3 = this.bitWriter.Pos;
int pos3 = this.bitWriter.NumBytes();
it.LumaBits = pos2 - pos1;
it.UvBits = pos3 - pos2;
it.BitCount[segment, i16 ? 1 : 0] += it.LumaBits;
@ -942,8 +944,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
uint coeff = (uint)((sign ? -input[j] : input[j]) + mtx.Sharpen[j]);
if (coeff > mtx.ZThresh[j])
{
uint q = (uint)mtx.Q[j];
uint iQ = (uint)mtx.IQ[j];
uint q = mtx.Q[j];
uint iQ = mtx.IQ[j];
uint b = mtx.Bias[j];
int level = this.QuantDiv(coeff, iQ, b);
if (level > MaxLevel)
@ -1342,16 +1344,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
private int GetSse(Span<byte> a, Span<byte> b, int w, int h)
{
int count = 0;
int aOffset = 0;
int bOffset = 0;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
int diff = a[x] - b[x];
int diff = a[aOffset + x] - b[bOffset + x];
count += diff * diff;
}
a = a.Slice(WebPConstants.Bps);
b = b.Slice(WebPConstants.Bps);
aOffset += WebPConstants.Bps;
bOffset += WebPConstants.Bps;
}
return count;

4
src/ImageSharp/Formats/WebP/Lossy/Vp8Matrix.cs

@ -74,10 +74,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossy
this.IQ[i] = (ushort)((1 << WebPConstants.QFix) / this.Q[i]);
this.Bias[i] = (uint)this.BIAS(bias);
// zthresh_ is the exact value such that QUANTDIV(coeff, iQ, B) is:
// zthresh is the exact value such that QUANTDIV(coeff, iQ, B) is:
// * zero if coeff <= zthresh
// * non-zero if coeff > zthresh
this.ZThresh[i] = (uint)(((1 << WebPConstants.QFix) - 1 - this.Bias[i]) / this.IQ[i]);
this.ZThresh[i] = ((1 << WebPConstants.QFix) - 1 - this.Bias[i]) / this.IQ[i];
}
for (i = 2; i < 16; ++i)

2
src/ImageSharp/Formats/WebP/WebPConstants.cs

@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <summary>
/// Header bytes identifying a lossless image.
/// </summary>
public static readonly byte[] Vp8LTag =
public static readonly byte[] Vp8LMagicBytes =
{
0x56, // V
0x50, // P

Loading…
Cancel
Save