Browse Source

baseline decode works progressive nearly

af/merge-core
James Jackson-South 9 years ago
parent
commit
55e9280a74
  1. 2
      src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs
  2. 222
      src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs
  3. 8
      src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs
  4. 38
      src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs
  5. 53
      src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs
  6. 63
      tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpeg.htm
  7. 1205
      tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpg.js

2
src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs

@ -17,7 +17,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
/// <summary>
/// Gets or sets the output
/// </summary>
public Buffer<byte> Output;
public Buffer<short> Output;
/// <summary>
/// Gets or sets the horizontal scaling factor

222
src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs

@ -0,0 +1,222 @@
namespace ImageSharp.Formats.Jpeg.Port.Components
{
using System;
using ImageSharp.Memory;
/// <summary>
/// Performa the invers
/// </summary>
internal static class IDCT
{
private const int DctCos1 = 4017; // cos(pi/16)
private const int DctSin1 = 799; // sin(pi/16)
private const int DctCos3 = 3406; // cos(3*pi/16)
private const int DctSin3 = 2276; // sin(3*pi/16)
private const int DctCos6 = 1567; // cos(6*pi/16)
private const int DctSin6 = 3784; // sin(6*pi/16)
private const int DctSqrt2 = 5793; // sqrt(2)
private const int DctSqrt1D2 = 2896; // sqrt(2) / 2
/// <summary>
/// A port of Poppler's IDCT method which in turn is taken from:
/// Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz,
/// 'Practical Fast 1-D DCT Algorithms with 11 Multiplications',
/// IEEE Intl. Conf. on Acoustics, Speech &amp; Signal Processing, 1989, 988-991.
/// </summary>
/// <param name="quantizationTables">The quantization tables</param>
/// <param name="component">The fram component</param>
/// <param name="blockBufferOffset">The block buffer offset</param>
/// <param name="computationBuffer">The computational buffer for holding temp values</param>
public static void QuantizeAndInverse(QuantizationTables quantizationTables, ref FrameComponent component, int blockBufferOffset, Buffer<short> computationBuffer)
{
Span<short> qt = quantizationTables.Tables.GetRowSpan(component.QuantizationIdentifier);
Buffer<short> blockData = component.BlockData;
int v0, v1, v2, v3, v4, v5, v6, v7;
int p0, p1, p2, p3, p4, p5, p6, p7;
int t;
// inverse DCT on rows
for (int row = 0; row < 64; row += 8)
{
// gather block data
p0 = blockData[blockBufferOffset + row];
p1 = blockData[blockBufferOffset + row + 1];
p2 = blockData[blockBufferOffset + row + 2];
p3 = blockData[blockBufferOffset + row + 3];
p4 = blockData[blockBufferOffset + row + 4];
p5 = blockData[blockBufferOffset + row + 5];
p6 = blockData[blockBufferOffset + row + 6];
p7 = blockData[blockBufferOffset + row + 7];
// dequant p0
p0 *= qt[row];
// check for all-zero AC coefficients
if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0)
{
t = ((DctSqrt2 * p0) + 512) >> 10;
short st = (short)t;
computationBuffer[row] = st;
computationBuffer[row + 1] = st;
computationBuffer[row + 2] = st;
computationBuffer[row + 3] = st;
computationBuffer[row + 4] = st;
computationBuffer[row + 5] = st;
computationBuffer[row + 6] = st;
computationBuffer[row + 7] = st;
continue;
}
// dequant p1 ... p7
p1 *= qt[row + 1];
p2 *= qt[row + 2];
p3 *= qt[row + 3];
p4 *= qt[row + 4];
p5 *= qt[row + 5];
p6 *= qt[row + 6];
p7 *= qt[row + 7];
// stage 4
v0 = ((DctSqrt2 * p0) + 128) >> 8;
v1 = ((DctSqrt2 * p4) + 128) >> 8;
v2 = p2;
v3 = p6;
v4 = ((DctSqrt1D2 * (p1 - p7)) + 128) >> 8;
v7 = ((DctSqrt1D2 * (p1 + p7)) + 128) >> 8;
v5 = p3 << 4;
v6 = p5 << 4;
// stage 3
v0 = (v0 + v1 + 1) >> 1;
v1 = v0 - v1;
t = ((v2 * DctSin6) + (v3 * DctCos6) + 128) >> 8;
v2 = ((v2 * DctCos6) - (v3 * DctSin6) + 128) >> 8;
v3 = t;
v4 = (v4 + v6 + 1) >> 1;
v6 = v4 - v6;
v7 = (v7 + v5 + 1) >> 1;
v5 = v7 - v5;
// stage 2
v0 = (v0 + v3 + 1) >> 1;
v3 = v0 - v3;
v1 = (v1 + v2 + 1) >> 1;
v2 = v1 - v2;
t = ((v4 * DctSin3) + (v7 * DctCos3) + 2048) >> 12;
v4 = ((v4 * DctCos3) - (v7 * DctSin3) + 2048) >> 12;
v7 = t;
t = ((v5 * DctSin1) + (v6 * DctCos1) + 2048) >> 12;
v5 = ((v5 * DctCos1) - (v6 * DctSin1) + 2048) >> 12;
v6 = t;
// stage 1
computationBuffer[row] = (short)(v0 + v7);
computationBuffer[row + 7] = (short)(v0 - v7);
computationBuffer[row + 1] = (short)(v1 + v6);
computationBuffer[row + 6] = (short)(v1 - v6);
computationBuffer[row + 2] = (short)(v2 + v5);
computationBuffer[row + 5] = (short)(v2 - v5);
computationBuffer[row + 3] = (short)(v3 + v4);
computationBuffer[row + 4] = (short)(v3 - v4);
}
// inverse DCT on columns
for (int col = 0; col < 8; ++col)
{
p0 = computationBuffer[col];
p1 = computationBuffer[col + 8];
p2 = computationBuffer[col + 16];
p3 = computationBuffer[col + 24];
p4 = computationBuffer[col + 32];
p5 = computationBuffer[col + 40];
p6 = computationBuffer[col + 48];
p7 = computationBuffer[col + 56];
// check for all-zero AC coefficients
if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0)
{
t = ((DctSqrt2 * p0) + 8192) >> 14;
// convert to 8 bit
t = (t < -2040) ? 0 : (t >= 2024) ? 255 : (t + 2056) >> 4;
short st = (short)t;
blockData[blockBufferOffset + col] = st;
blockData[blockBufferOffset + col + 8] = st;
blockData[blockBufferOffset + col + 16] = st;
blockData[blockBufferOffset + col + 24] = st;
blockData[blockBufferOffset + col + 32] = st;
blockData[blockBufferOffset + col + 40] = st;
blockData[blockBufferOffset + col + 48] = st;
blockData[blockBufferOffset + col + 56] = st;
continue;
}
// stage 4
v0 = ((DctSqrt2 * p0) + 2048) >> 12;
v1 = ((DctSqrt2 * p4) + 2048) >> 12;
v2 = p2;
v3 = p6;
v4 = ((DctSqrt1D2 * (p1 - p7)) + 2048) >> 12;
v7 = ((DctSqrt1D2 * (p1 + p7)) + 2048) >> 12;
v5 = p3;
v6 = p5;
// stage 3
// Shift v0 by 128.5 << 5 here, so we don't need to shift p0...p7 when
// converting to UInt8 range later.
v0 = ((v0 + v1 + 1) >> 1) + 4112;
v1 = v0 - v1;
t = ((v2 * DctSin6) + (v3 * DctCos6) + 2048) >> 12;
v2 = ((v2 * DctCos6) - (v3 * DctSin6) + 2048) >> 12;
v3 = t;
v4 = (v4 + v6 + 1) >> 1;
v6 = v4 - v6;
v7 = (v7 + v5 + 1) >> 1;
v5 = v7 - v5;
// stage 2
v0 = (v0 + v3 + 1) >> 1;
v3 = v0 - v3;
v1 = (v1 + v2 + 1) >> 1;
v2 = v1 - v2;
t = ((v4 * DctSin3) + (v7 * DctCos3) + 2048) >> 12;
v4 = ((v4 * DctCos3) - (v7 * DctSin3) + 2048) >> 12;
v7 = t;
t = ((v5 * DctSin1) + (v6 * DctCos1) + 2048) >> 12;
v5 = ((v5 * DctCos1) - (v6 * DctSin1) + 2048) >> 12;
v6 = t;
// stage 1
p0 = v0 + v7;
p7 = v0 - v7;
p1 = v1 + v6;
p6 = v1 - v6;
p2 = v2 + v5;
p5 = v2 - v5;
p3 = v3 + v4;
p4 = v3 - v4;
// convert to 8-bit integers
p0 = (p0 < 16) ? 0 : (p0 >= 4080) ? 255 : p0 >> 4;
p1 = (p1 < 16) ? 0 : (p1 >= 4080) ? 255 : p1 >> 4;
p2 = (p2 < 16) ? 0 : (p2 >= 4080) ? 255 : p2 >> 4;
p3 = (p3 < 16) ? 0 : (p3 >= 4080) ? 255 : p3 >> 4;
p4 = (p4 < 16) ? 0 : (p4 >= 4080) ? 255 : p4 >> 4;
p5 = (p5 < 16) ? 0 : (p5 >= 4080) ? 255 : p5 >> 4;
p6 = (p6 < 16) ? 0 : (p6 >= 4080) ? 255 : p6 >> 4;
p7 = (p7 < 16) ? 0 : (p7 >= 4080) ? 255 : p7 >> 4;
// store block data
blockData[blockBufferOffset + col] = (short)p0;
blockData[blockBufferOffset + col + 8] = (short)p1;
blockData[blockBufferOffset + col + 16] = (short)p2;
blockData[blockBufferOffset + col + 24] = (short)p3;
blockData[blockBufferOffset + col + 32] = (short)p4;
blockData[blockBufferOffset + col + 40] = (short)p5;
blockData[blockBufferOffset + col + 48] = (short)p6;
blockData[blockBufferOffset + col + 56] = (short)p7;
}
}
}
}

8
src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs

@ -46,7 +46,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
/// <summary>
/// Gets or sets the quantization tables.
/// </summary>
public Buffer2D<short> Tables { get; set; }
public Buffer2D<short> Tables
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get; set;
}
= new Buffer2D<short>(64, 4);
/// <inheritdoc/>
public void Dispose()

38
src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs

@ -41,6 +41,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
private int successiveACState;
private int successiveACNextValue;
/// <summary>
/// Decodes the spectral scan
/// </summary>
@ -91,6 +93,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
{
decodeFn = this.DecodeDCSuccessive;
}
Debug.WriteLine(successivePrev == 0 ? "decodeDCFirst" : "decodeDCSuccessive");
}
else
{
@ -102,6 +106,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
{
decodeFn = this.DecodeACSuccessive;
}
Debug.WriteLine(successivePrev == 0 ? "decodeACFirst" : "decodeACSuccessive");
}
}
else
@ -120,16 +126,28 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
mcuExpected = mcusPerLine * frame.McusPerColumn;
}
Debug.WriteLine("mcuExpected = " + mcuExpected);
// FileMarker fileMarker;
while (mcu < mcuExpected)
{
// Reset interval stuff
// Reset interval
int mcuToRead = resetInterval > 0 ? Math.Min(mcuExpected - mcu, resetInterval) : mcuExpected;
for (int i = 0; i < componentsLength; i++)
// TODO: We might just be able to loop here.
if (componentsLength == 1)
{
ref FrameComponent c = ref components[i];
ref FrameComponent c = ref components[componentIndex];
c.Pred = 0;
}
else
{
for (int i = 0; i < componentsLength; i++)
{
ref FrameComponent c = ref components[i];
c.Pred = 0;
}
}
this.eobrun = 0;
@ -165,8 +183,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
}
// Find marker
this.bitsCount = 0;
// this.bitsCount = 0;
// // TODO: We need to make sure we are not overwriting anything here.
// fileMarker = JpegDecoderCore.FindNextFileMarker(stream);
// // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past
@ -205,7 +222,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetBlockBufferOffset(FrameComponent component, int row, int col)
private static int GetBlockBufferOffset(ref FrameComponent component, int row, int col)
{
return 64 * (((component.BlocksPerLine + 1) * row) + col);
}
@ -217,7 +234,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
int mcuCol = mcu % mcusPerLine;
int blockRow = (mcuRow * component.VerticalFactor) + row;
int blockCol = (mcuCol * component.HorizontalFactor) + col;
int offset = GetBlockBufferOffset(component, blockRow, blockCol);
int offset = GetBlockBufferOffset(ref component, blockRow, blockCol);
decode(ref component, offset, dcHuffmanTables, acHuffmanTables, stream);
}
@ -226,7 +243,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
{
int blockRow = (mcu / component.BlocksPerLine) | 0;
int blockCol = mcu % component.BlocksPerLine;
int offset = GetBlockBufferOffset(component, blockRow, blockCol);
int offset = GetBlockBufferOffset(ref component, blockRow, blockCol);
decode(ref component, offset, dcHuffmanTables, acHuffmanTables, stream);
}
@ -394,7 +411,6 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
while (k <= e)
{
byte z = QuantizationTables.DctZigZag[k];
int successiveACNextValue = 0;
switch (this.successiveACState)
{
case 0: // Initial state
@ -421,7 +437,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
throw new ImageFormatException("Invalid ACn encoding");
}
successiveACNextValue = this.ReceiveAndExtend(s, stream);
this.successiveACNextValue = this.ReceiveAndExtend(s, stream);
this.successiveACState = r > 0 ? 2 : 3;
}
@ -449,7 +465,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
}
else
{
component.BlockData[offset + z] = (short)(successiveACNextValue << this.successiveState);
component.BlockData[offset + z] = (short)(this.successiveACNextValue << this.successiveState);
this.successiveACState = 0;
}

53
src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs

@ -7,6 +7,7 @@ namespace ImageSharp.Formats.Jpeg.Port
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
@ -198,6 +199,12 @@ namespace ImageSharp.Formats.Jpeg.Port
this.quantizationTables = null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetBlockBufferOffset(ref Component component, int row, int col)
{
return 64 * (((component.BlocksPerLine + 1) * row) + col);
}
private void ParseStream()
{
// Check for the Start Of Image marker.
@ -325,7 +332,7 @@ namespace ImageSharp.Formats.Jpeg.Port
BlocksPerColumn = frameComponent.BlocksPerColumn
};
this.BuildComponentData(ref component);
this.BuildComponentData(ref component, ref frameComponent);
this.components.Components[i] = component;
}
@ -422,8 +429,6 @@ namespace ImageSharp.Formats.Jpeg.Port
/// </exception>
private void ProcessDqtMarker(int remaining)
{
// Pooled. Disposed on disposal of decoder
this.quantizationTables.Tables = new Buffer2D<short>(64, 4);
while (remaining > 0)
{
bool done = false;
@ -622,10 +627,10 @@ namespace ImageSharp.Formats.Jpeg.Port
private void ProcessStartOfScanMarker()
{
int selectorsCount = this.InputStream.ReadByte();
int index = -1;
int componentIndex = -1;
for (int i = 0; i < selectorsCount; i++)
{
index = -1;
componentIndex = -1;
int selector = this.InputStream.ReadByte();
for (int j = 0; j < this.frame.ComponentIds.Length; j++)
@ -633,16 +638,16 @@ namespace ImageSharp.Formats.Jpeg.Port
byte id = this.frame.ComponentIds[j];
if (selector == id)
{
index = j;
componentIndex = j;
}
}
if (index < 0)
if (componentIndex < 0)
{
throw new ImageFormatException("Unknown component selector");
}
ref FrameComponent component = ref this.frame.Components[index];
ref FrameComponent component = ref this.frame.Components[componentIndex];
int tableSpec = this.InputStream.ReadByte();
component.DCHuffmanTableId = tableSpec >> 4;
component.ACHuffmanTableId = tableSpec & 15;
@ -661,22 +666,50 @@ namespace ImageSharp.Formats.Jpeg.Port
this.dcHuffmanTables,
this.acHuffmanTables,
this.frame.Components,
index,
componentIndex,
selectorsCount,
this.resetInterval,
spectralStart,
spectralEnd,
successiveApproximation >> 4,
successiveApproximation & 15);
Debug.WriteLine("spectralStart= " + spectralStart);
Debug.WriteLine("spectralEnd= " + spectralEnd);
Debug.WriteLine("successiveApproximation= " + successiveApproximation);
Debug.WriteLine("Components after");
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 10; j++)
{
Debug.WriteLine("component [" + i + "] : value [" + j + "] =" + this.frame.Components[i].BlockData[j] + "]");
}
}
}
/// <summary>
/// Build the data for the given component
/// </summary>
/// <param name="component">The component</param>
private void BuildComponentData(ref Component component)
/// <param name="frameComponent">The frame component</param>
private void BuildComponentData(ref Component component, ref FrameComponent frameComponent)
{
// TODO: Write this
int blocksPerLine = component.BlocksPerLine;
int blocksPerColumn = component.BlocksPerColumn;
using (var computationBuffer = Buffer<short>.CreateClean(64))
{
for (int blockRow = 0; blockRow < blocksPerColumn; blockRow++)
{
for (int blockCol = 0; blockCol < blocksPerLine; blockCol++)
{
int offset = GetBlockBufferOffset(ref component, blockRow, blockCol);
IDCT.QuantizeAndInverse(this.quantizationTables, ref frameComponent, offset, computationBuffer);
}
}
}
component.Output = frameComponent.BlockData;
}
/// <summary>

63
tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpeg.htm

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<input type="file" id="files" />
<div id="output"></div>
<script src="jpg.js"></script>
<script>
(function (document) {
var input = document.getElementById("files"),
output = document.getElementById('output'),
fileData; // We need fileData to be visible to getBuffer.
// Eventhandler for file input.
function openfile(evt) {
var files = input.files;
// Pass the file to the blob, not the input[0].
fileData = new Blob([files[0]]);
// Pass getBuffer to promise.
var promise = new Promise(getBuffer);
// Wait for promise to be resolved, or log error.
promise.then(function (data) {
// Here you can pass the bytes to another function.
// output.innerHTML = data.toString();
// console.log(data);
console.log(new Date());
var jpeg = new JpegImage();
jpeg.parse(data);
var d = jpeg.getData(804, 1198, true);
// output.innerHTML = d.toString();
console.log(new Date());
console.log(d);
}).catch(function (err) {
console.log('Error: ', err);
});
}
/*
Create a function which will be passed to the promise
and resolve it when FileReader has finished loading the file.
*/
function getBuffer(resolve) {
var reader = new FileReader();
reader.readAsArrayBuffer(fileData);
reader.onload = function () {
var arrayBuffer = reader.result
var bytes = new Uint8Array(arrayBuffer);
resolve(bytes);
}
}
// Eventlistener for file input.
input.addEventListener('change', openfile, false);
}(document));
</script>
</body>
</html>

1205
tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpg.js

File diff suppressed because it is too large
Loading…
Cancel
Save