Browse Source

JpegColorConverter

pull/322/head
Anton Firszov 9 years ago
parent
commit
f65eb8b4ca
  1. 47
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs
  2. 70
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs
  3. 38
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs
  4. 6
      src/ImageSharp/Memory/Buffer.cs
  5. 18
      src/ImageSharp/Memory/IBuffer.cs
  6. 81
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

47
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs

@ -0,0 +1,47 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.ColorSpaces;
using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.YCbCrColorSapce;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
internal abstract partial class JpegColorConverter
{
private class FromYCbCr : JpegColorConverter
{
private static readonly YCbCrAndRgbConverter Converter = new YCbCrAndRgbConverter();
public FromYCbCr()
: base(JpegColorSpace.YCbCr)
{
}
public override void ConvertToRGBA(ComponentValues values, Span<Vector4> result)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> yVals = values.Component0;
ReadOnlySpan<float> cbVals = values.Component1;
ReadOnlySpan<float> crVals = values.Component2;
Vector4 rgbaVector = new Vector4(0, 0, 0, 1);
for (int i = 0; i < result.Length; i++)
{
float colY = yVals[i];
float colCb = cbVals[i];
float colCr = crVals[i];
YCbCr yCbCr = new YCbCr(colY, colCb, colCr);
// Slow conversion for now:
Rgb rgb = Converter.Convert(yCbCr);
Unsafe.As<Vector4, Vector3>(ref rgbaVector) = rgb.Vector;
result[i] = rgbaVector;
}
}
}
}
}

70
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
internal abstract partial class JpegColorConverter
{
private static readonly JpegColorConverter[] Converters = { new FromYCbCr(), };
protected JpegColorConverter(JpegColorSpace colorSpace)
{
this.ColorSpace = colorSpace;
}
public JpegColorSpace ColorSpace { get; }
public static JpegColorConverter GetConverter(JpegColorSpace colorSpace)
{
JpegColorConverter converter = Converters.FirstOrDefault(c => c.ColorSpace == colorSpace);
if (converter == null)
{
throw new Exception($"Could not find any converter for JpegColorSpace {colorSpace}!");
}
return converter;
}
public abstract void ConvertToRGBA(ComponentValues values, Span<Vector4> result);
public struct ComponentValues
{
public readonly int ComponentCount;
public readonly ReadOnlySpan<float> Component0;
public readonly ReadOnlySpan<float> Component1;
public readonly ReadOnlySpan<float> Component2;
public readonly ReadOnlySpan<float> Component3;
public ComponentValues(IReadOnlyList<IBuffer2D<float>> componentBuffers, int row)
{
this.ComponentCount = componentBuffers.Count;
this.Component0 = componentBuffers[0].GetRowSpan(row);
this.Component1 = Span<float>.Empty;
this.Component2 = Span<float>.Empty;
this.Component3 = Span<float>.Empty;
if (this.ComponentCount > 1)
{
this.Component1 = componentBuffers[1].GetRowSpan(row);
if (this.ComponentCount > 2)
{
this.Component2 = componentBuffers[2].GetRowSpan(row);
if (this.ComponentCount > 3)
{
this.Component3 = componentBuffers[3].GetRowSpan(row);
}
}
}
}
}
}
}

38
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs

@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.ColorSpaces;
using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.YCbCrColorSapce;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@ -15,6 +17,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
public const int PixelRowsPerStep = 4 * 8;
private readonly Buffer<Vector4> rgbaBuffer;
private JpegColorConverter colorConverter;
public JpegImagePostProcessor(IRawJpegData rawJpeg)
{
this.RawJpeg = rawJpeg;
@ -23,6 +29,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep);
this.ComponentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(this, c)).ToArray();
this.rgbaBuffer = new Buffer<Vector4>(rawJpeg.ImageSizeInPixels.Width);
this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace);
}
public JpegComponentPostProcessor[] ComponentProcessors { get; }
@ -41,6 +49,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
cpp.Dispose();
}
this.rgbaBuffer.Dispose();
}
public bool DoPostProcessorStep<TPixel>(Image<TPixel> destination)
@ -65,6 +75,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
public void PostProcess<TPixel>(Image<TPixel> destination)
where TPixel : struct, IPixel<TPixel>
{
if (this.RawJpeg.ImageSizeInPixels != destination.Size())
{
throw new ArgumentException("Input image is not of the size of the processed one!");
}
while (this.DoPostProcessorStep(destination))
{
}
@ -75,31 +90,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
int maxY = Math.Min(destination.Height, this.CurrentImageRowInPixels + PixelRowsPerStep);
JpegComponentPostProcessor[] cp = this.ComponentProcessors;
YCbCrAndRgbConverter converter = new YCbCrAndRgbConverter();
Vector4 rgbaVector = new Vector4(0, 0, 0, 1);
Buffer2D<float>[] buffers = this.ComponentProcessors.Select(cp => cp.ColorBuffer).ToArray();
for (int yy = this.CurrentImageRowInPixels; yy < maxY; yy++)
{
int y = yy - this.CurrentImageRowInPixels;
Span<TPixel> destRow = destination.GetRowSpan(yy);
for (int x = 0; x < destination.Width; x++)
{
float colY = cp[0].ColorBuffer[x, y];
float colCb = cp[1].ColorBuffer[x, y];
float colCr = cp[2].ColorBuffer[x, y];
YCbCr yCbCr = new YCbCr(colY, colCb, colCr);
Rgb rgb = converter.Convert(yCbCr);
var values = new JpegColorConverter.ComponentValues(buffers, y);
this.colorConverter.ConvertToRGBA(values, this.rgbaBuffer);
Unsafe.As<Vector4, Vector3>(ref rgbaVector) = rgb.Vector;
Span<TPixel> destRow = destination.GetRowSpan(yy);
destRow[x].PackFromVector4(rgbaVector);
}
PixelOperations<TPixel>.Instance.PackFromVector4(this.rgbaBuffer, destRow, destination.Width);
}
}
}

6
src/ImageSharp/Memory/Buffer.cs

@ -7,12 +7,13 @@ using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Memory
{
/// <inheritdoc />
/// <summary>
/// Manages a buffer of value type objects as a Disposable resource.
/// The backing array is either pooled or comes from the outside.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
internal class Buffer<T> : IDisposable
internal class Buffer<T> : IBuffer<T>
where T : struct
{
/// <summary>
@ -205,7 +206,8 @@ namespace SixLabors.ImageSharp.Memory
{
if (this.IsDisposedOrLostArrayOwnership)
{
throw new InvalidOperationException("TakeArrayOwnership() is invalid: either Buffer<T> is disposed or TakeArrayOwnership() has been called multiple times!");
throw new InvalidOperationException(
"TakeArrayOwnership() is invalid: either Buffer<T> is disposed or TakeArrayOwnership() has been called multiple times!");
}
this.IsDisposedOrLostArrayOwnership = true;

18
src/ImageSharp/Memory/IBuffer.cs

@ -0,0 +1,18 @@
using System;
namespace SixLabors.ImageSharp.Memory
{
/// <inheritdoc />
/// <summary>
/// Represents a contigous memory buffer of value-type items "promising" a <see cref="T:System.Span`1" />
/// </summary>
/// <typeparam name="T">The value type</typeparam>
internal interface IBuffer<T> : IDisposable
where T : struct
{
/// <summary>
/// Gets the span to the memory "promised" by this buffer
/// </summary>
Span<T> Span { get; }
}
}

81
tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

@ -0,0 +1,81 @@
using System;
using System.Numerics;
using SixLabors.ImageSharp.ColorSpaces;
using SixLabors.ImageSharp.ColorSpaces.Conversion;
using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.YCbCrColorSapce;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Tests.Colorspaces;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using Xunit;
using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
public class JpegColorConverterTests
{
private const float Precision = 0.1f;
private const int InputBufferLength = 42;
// The result buffer could be shorter
private const int ResultBufferLength = 40;
private readonly Vector4[] Result = new Vector4[ResultBufferLength];
private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter();
public JpegColorConverterTests(ITestOutputHelper output)
{
this.Output = output;
}
private ITestOutputHelper Output { get; }
private static JpegColorConverter.ComponentValues CreateRandomValues(int componentCount, float maxVal = 255f)
{
var rnd = new Random(42);
Buffer2D<float>[] buffers = new Buffer2D<float>[componentCount];
for (int i = 0; i < componentCount; i++)
{
float[] values = new float[InputBufferLength];
for (int j = 0; j < InputBufferLength; j++)
{
values[j] = (float)rnd.NextDouble() * maxVal;
}
// no need to dispose when buffer is not array owner
buffers[i] = new Buffer2D<float>(values, values.Length, 1);
}
return new JpegColorConverter.ComponentValues(buffers, 0);
}
[Fact]
public void ConvertFromYCbCr()
{
var converter = JpegColorConverter.GetConverter(JpegColorSpace.YCbCr);
JpegColorConverter.ComponentValues values = CreateRandomValues(3);
converter.ConvertToRGBA(values, this.Result);
for (int i = 0; i < ResultBufferLength; i++)
{
float y = values.Component0[i];
float cb = values.Component1[i];
float cr = values.Component2[i];
YCbCr ycbcr = new YCbCr(y, cb, cr);
Vector4 rgba = this.Result[i];
Rgb actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
Rgb expected = ColorSpaceConverter.ToRgb(ycbcr);
Assert.True(actual.AlmostEquals(expected, Precision));
Assert.Equal(1, rgba.W);
}
}
}
}
Loading…
Cancel
Save