Browse Source

fineshed CopyTo(area), introduced IRawJpegData

af/merge-core
Anton Firszov 9 years ago
parent
commit
bd24e375bb
  1. 4
      src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs
  2. 63
      src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs
  3. 41
      src/ImageSharp/Formats/Jpeg/Common/ComponentPostProcessor.cs
  4. 19
      src/ImageSharp/Formats/Jpeg/Common/ComponentUtils.cs
  5. 16
      src/ImageSharp/Formats/Jpeg/Common/IRawJpegData.cs
  6. 33
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/ComponentPostprocessor.cs
  7. 2
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs
  8. 7
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs
  9. 33
      src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
  10. 5
      src/ImageSharp/Memory/BufferArea.cs
  11. 101
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs
  12. 20
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
  13. 4
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs
  14. 1
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  15. 3
      tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs
  16. 8
      tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs
  17. 14
      tests/ImageSharp.Tests/Memory/BufferAreaTests.cs

4
src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs

@ -5,6 +5,8 @@ using System.Text;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
using SixLabors.ImageSharp.Memory;
/// <summary>
/// Represents a Jpeg block with <see cref="short"/> coefficiens.
/// </summary>
@ -44,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
}
}
public short this[int y, int x]
public short this[int x, int y]
{
get => this[(y * 8) + x];
set => this[(y * 8) + x] = value;

63
src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs

@ -10,6 +10,8 @@ using System.Runtime.InteropServices;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
using SixLabors.ImageSharp.Memory;
/// <summary>
/// Represents a Jpeg block with <see cref="float"/> coefficients.
/// </summary>
@ -83,7 +85,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
}
}
public float this[int y, int x]
public float this[int x, int y]
{
get => this[(y * 8) + x];
set => this[(y * 8) + x] = value;
@ -304,6 +306,60 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row)
{
ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float));
ref byte d = ref Unsafe.Add(ref destBase, row * destStride);
Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float));
}
public void CopyTo(BufferArea<float> area)
{
ref byte selfBase = ref Unsafe.As<Block8x8F, byte>(ref this);
ref byte destBase = ref Unsafe.As<float, byte>(ref area.DangerousGetPinnableReference());
int destStride = area.Stride * sizeof(float);
CopyRowImpl(ref selfBase, ref destBase, destStride, 0);
CopyRowImpl(ref selfBase, ref destBase, destStride, 1);
CopyRowImpl(ref selfBase, ref destBase, destStride, 2);
CopyRowImpl(ref selfBase, ref destBase, destStride, 3);
CopyRowImpl(ref selfBase, ref destBase, destStride, 4);
CopyRowImpl(ref selfBase, ref destBase, destStride, 5);
CopyRowImpl(ref selfBase, ref destBase, destStride, 6);
CopyRowImpl(ref selfBase, ref destBase, destStride, 7);
}
public void CopyTo(BufferArea<float> area, int horizontalScale, int verticalScale)
{
if (horizontalScale == 1 && verticalScale == 1)
{
this.CopyTo(area);
return;
}
// TODO: Optimize: implement all the cases with loopless special code! (T4?)
for (int y = 0; y < 8; y++)
{
int yy = y * verticalScale;
for (int x = 0; x < 8; x++)
{
int xx = x * horizontalScale;
float value = this[(y * 8) + x];
for (int i = 0; i < verticalScale; i++)
{
for (int j = 0; j < horizontalScale; j++)
{
area[xx + j, yy + i] = value;
}
}
}
}
}
public float[] ToArray()
{
float[] result = new float[Size];
@ -520,5 +576,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
DebugGuard.MustBeLessThan(idx, Size, nameof(idx));
DebugGuard.MustBeGreaterThanOrEqualTo(idx, 0, nameof(idx));
}
[StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(float))]
private struct Row
{
}
}
}

41
src/ImageSharp/Formats/Jpeg/Common/ComponentPostProcessor.cs

@ -0,0 +1,41 @@
using System;
using System.Linq;
using SixLabors.ImageSharp.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
internal class JpegPostProcessor
{
private ComponentPostProcessor[] componentProcessors;
public JpegPostProcessor(IRawJpegData data)
{
this.Data = data;
this.componentProcessors = data.Components.Select(c => new ComponentPostProcessor(this, c)).ToArray();
}
public IRawJpegData Data { get; }
}
internal class ComponentPostProcessor : IDisposable
{
public ComponentPostProcessor(JpegPostProcessor jpegPostProcessor, IJpegComponent component)
{
this.Component = component;
this.JpegPostProcessor = jpegPostProcessor;
}
public JpegPostProcessor JpegPostProcessor { get; }
public IJpegComponent Component { get; }
public int NumberOfRowGroupSteps { get; }
public Buffer2D<float> ColorBuffer { get; }
public void Dispose()
{
}
}
}

19
src/ImageSharp/Formats/Jpeg/Common/ComponentUtils.cs

@ -13,6 +13,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
public static Size SizeInBlocks(this IJpegComponent component) => new Size(component.WidthInBlocks, component.HeightInBlocks);
public static ref Block8x8 GetBlockReference(this IJpegComponent component, int bx, int by)
{
return ref component.SpectralBlocks[bx, by];
}
public static SubsampleRatio GetSubsampleRatio(int horizontalRatio, int verticalRatio)
{
switch ((horizontalRatio << 4) | verticalRatio)
@ -107,19 +112,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
return sizes;
}
//public static Size CalculateJpegChannelSize(this IJpegComponent component, SubsampleRatio ratio = SubsampleRatio.Undefined)
//{
// Size size = new Size(component.WidthInBlocks, component.HeightInBlocks) * 8;
// if (component.IsChromaComponent())
// {
// return ratio.CalculateChrominanceSize(size.Width, size.Height);
// }
// else
// {
// return size;
// }
//}
}
}

16
src/ImageSharp/Formats/Jpeg/Common/IRawJpegData.cs

@ -0,0 +1,16 @@
using System.Collections.Generic;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
internal interface IRawJpegData
{
Size ImageSize { get; }
Size ImageSizeInBlocks { get; }
int ComponentCount { get; }
IEnumerable<IJpegComponent> Components { get; }
}
}

33
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/ComponentPostprocessor.cs

@ -1,33 +0,0 @@
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
internal class ComponentPostProcessor : IDisposable
{
public Size ImageSizeInBlocks { get; }
public int NumberOfRowGroupScans
{
get;
}
class RowGroupProcessor : IDisposable
{
public Buffer2D<float> ColorBuffer { get; }
public void Dispose()
{
}
}
public void Dispose()
{
}
}
}

2
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs

@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <param name="component">The <see cref="OrigComponent"/></param>
/// <param name="bx">The x index of the block in <see cref="OrigComponent.SpectralBlocks"/></param>
/// <param name="by">The y index of the block in <see cref="OrigComponent.SpectralBlocks"/></param>
private void ProcessBlockColors(OrigJpegDecoderCore decoder, OrigComponent component, int bx, int by)
private void ProcessBlockColors(OrigJpegDecoderCore decoder, IJpegComponent component, int bx, int by)
{
ref Block8x8 sourceBlock = ref component.GetBlockReference(bx, by);

7
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs

@ -52,12 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <inheritdoc />
public int HeightInBlocks { get; private set; }
public ref Block8x8 GetBlockReference(int bx, int by)
{
return ref this.SpectralBlocks[bx, by];
}
/// <summary>
/// Initializes <see cref="SpectralBlocks"/>
/// </summary>

33
src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs

@ -18,10 +18,13 @@ using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{
using System.Collections.Generic;
using System.Numerics;
/// <summary>
/// Performs the jpeg decoding operation.
/// </summary>
internal sealed unsafe class OrigJpegDecoderCore : IDisposable
internal sealed unsafe class OrigJpegDecoderCore : IDisposable, IRawJpegData
{
/// <summary>
/// The maximum number of color components
@ -138,20 +141,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary>
public byte[] Temp { get; }
public Size ImageSize { get; private set; }
public Size ImageSizeInBlocks { get; private set; }
/// <summary>
/// Gets the number of color components within the image.
/// </summary>
public int ComponentCount { get; private set; }
IEnumerable<IJpegComponent> IRawJpegData.Components => this.Components;
/// <summary>
/// Gets the image height
/// </summary>
public int ImageHeight { get; private set; }
public int ImageHeight => this.ImageSize.Height;
/// <summary>
/// Gets the image width
/// </summary>
public int ImageWidth { get; private set; }
public int ImageWidth => this.ImageSize.Width;
/// <summary>
/// Gets the input stream.
@ -1167,8 +1176,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
throw new ImageFormatException("Only 8-Bit precision supported.");
}
this.ImageHeight = (this.Temp[1] << 8) + this.Temp[2];
this.ImageWidth = (this.Temp[3] << 8) + this.Temp[4];
int height = (this.Temp[1] << 8) + this.Temp[2];
int width = (this.Temp[3] << 8) + this.Temp[4];
this.InitSizes(width, height);
if (this.Temp[5] != this.ComponentCount)
{
throw new ImageFormatException("SOF has wrong length");
@ -1197,5 +1209,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.SubsampleRatio = ComponentUtils.GetSubsampleRatio(this.Components);
}
private void InitSizes(int width, int height)
{
this.ImageSize = new Size(width, height);
var sizeInBlocks = (Vector2)(SizeF)this.ImageSize;
sizeInBlocks /= 8;
sizeInBlocks.X = MathF.Ceiling(sizeInBlocks.X);
sizeInBlocks.Y = MathF.Ceiling(sizeInBlocks.Y);
this.ImageSizeInBlocks = new Size((int)sizeInBlocks.X, (int)sizeInBlocks.Y);
}
}
}

5
src/ImageSharp/Memory/BufferArea.cs

@ -34,8 +34,13 @@ namespace SixLabors.ImageSharp.Memory
public Size Size => this.Rectangle.Size;
public int Stride => this.DestinationBuffer.Width;
public ref T this[int x, int y] => ref this.DestinationBuffer.Span[this.GetIndexOf(x, y)];
public ref T DangerousGetPinnableReference() =>
ref this.DestinationBuffer.Span[(this.Rectangle.Y * this.DestinationBuffer.Width) + this.Rectangle.X];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<T> GetRowSpan(int y)
{

101
tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs

@ -0,0 +1,101 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
// Uncomment this to turn unit tests into benchmarks:
//#define BENCHMARKING
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using SixLabors.Primitives;
using Xunit;
using Xunit.Abstractions;
public partial class Block8x8FTests : JpegFixture
{
public class CopyToBufferArea : JpegFixture
{
public CopyToBufferArea(ITestOutputHelper output)
: base(output)
{
}
private static void VerifyAllZeroOutsideSubArea(Buffer2D<float> buffer, int subX, int subY, int horizontalFactor = 1, int verticalFactor = 1)
{
for (int y = 0; y < 20; y++)
{
for (int x = 0; x < 20; x++)
{
if (x < subX || x >= subX + 8 * horizontalFactor || y < subY || y >= subY + 8 * verticalFactor)
{
Assert.Equal(0, buffer[x, y]);
}
}
}
}
[Fact]
public void Unscaled()
{
Block8x8F block = CreateRandomFloatBlock(0, 100);
using (var buffer = new Buffer2D<float>(20, 20))
{
BufferArea<float> area = buffer.GetArea(5, 10, 8, 8);
block.CopyTo(area);
Assert.Equal(block[0, 0], buffer[5, 10]);
Assert.Equal(block[1, 0], buffer[6, 10]);
Assert.Equal(block[0, 1], buffer[5, 11]);
Assert.Equal(block[0, 7], buffer[5, 17]);
Assert.Equal(block[63], buffer[12, 17]);
VerifyAllZeroOutsideSubArea(buffer, 5, 10);
}
}
[Theory]
[InlineData(1, 1)]
[InlineData(1, 2)]
[InlineData(2, 1)]
[InlineData(2, 2)]
[InlineData(4, 2)]
[InlineData(4, 4)]
public void Scaled(int horizontalFactor, int verticalFactor)
{
Block8x8F block = CreateRandomFloatBlock(0, 100);
var start = new Point(50, 50);
using (var buffer = new Buffer2D<float>(100, 100))
{
BufferArea<float> area = buffer.GetArea(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor);
block.CopyTo(area, horizontalFactor, verticalFactor);
for (int y = 0; y < 8 * verticalFactor; y++)
{
for (int x = 0; x < 8 * horizontalFactor; x++)
{
int yy = y / verticalFactor;
int xx = x / horizontalFactor;
float expected = block[xx, yy];
float actual = area[x, y];
Assert.Equal(expected, actual);
}
}
VerifyAllZeroOutsideSubArea(buffer, start.X, start.Y, horizontalFactor, verticalFactor);
}
}
}
}
}

20
tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using Xunit;
using Xunit.Abstractions;
public class Block8x8FTests : JpegFixture
public partial class Block8x8FTests : JpegFixture
{
#if BENCHMARKING
public const int Times = 1000000;
@ -313,23 +313,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(expected, actual);
}
}
//[Fact]
//public void AsInt16Block()
//{
// float[] data = Create8x8FloatData();
// var source = default(Block8x8F);
// source.LoadFrom(data);
// Block8x8 dest = source.AsInt16Block();
// for (int i = 0; i < Block8x8F.Size; i++)
// {
// Assert.Equal((short)data[i], dest[i]);
// }
//}
[Fact]
public void RoundInto()
{

4
tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs

@ -115,12 +115,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
[Fact]
public void IndexerYX()
public void IndexerXY()
{
var block = default(Block8x8);
block[8 * 3 + 5] = 42;
short value = block[3, 5];
short value = block[5, 3];
Assert.Equal(42, value);
}

1
tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

@ -28,6 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk,
TestImages.Jpeg.Baseline.Jpeg400, TestImages.Jpeg.Baseline.Jpeg444,
TestImages.Jpeg.Baseline.Testorig420,
TestImages.Jpeg.Baseline.Jpeg420Small,
TestImages.Jpeg.Baseline.Bad.BadEOF,
TestImages.Jpeg.Baseline.Bad.ExifUndefType,
};

3
tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs

@ -103,6 +103,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
return result;
}
internal static Block8x8F CreateRandomFloatBlock(float minValue, float maxValue, int seed = 42) =>
Block8x8F.Load(Create8x8RandomFloatData(minValue, maxValue, seed));
internal void Print8x8Data<T>(T[] data) => this.Print8x8Data(new Span<T>(data));
internal void Print8x8Data<T>(Span<T> data)

8
tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs

@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
}
tmp += CosLut[y, v] * tmp2;
}
res[y, x] = (float)tmp;
res[y * 8 + x] = (float)tmp;
}
}
return res;
@ -88,11 +88,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
tmp2 = 0.0;
for (x = 0; x < 8; x++)
{
tmp2 += (double)block[y,x] * CosLut[x,u];
tmp2 += (double)block[y * 8 + x] * CosLut[x,u];
}
tmp += CosLut[y,v] * tmp2;
tmp += CosLut[y, v] * tmp2;
}
res[v,u] = (float) tmp;
res[v * 8 + u] = (float) tmp;
}
}

14
tests/ImageSharp.Tests/Memory/BufferAreaTests.cs

@ -117,5 +117,19 @@ namespace SixLabors.ImageSharp.Tests.Memory
Assert.Equal(value00, area1[0, 0]);
}
}
[Fact]
public void DangerousGetPinnableReference()
{
using (Buffer2D<int> buffer = CreateTestBuffer(20, 30))
{
BufferArea<int> area0 = buffer.GetArea(6, 8, 10, 10);
ref int r = ref area0.DangerousGetPinnableReference();
int expected = buffer[6, 8];
Assert.Equal(expected, r);
}
}
}
}
Loading…
Cancel
Save