Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jpeg 12 bit support #784

Merged
merged 8 commits into from
Dec 30, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/ImageSharp/Common/Tuples/Vector4Pair.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,21 @@ public void AddInplace(ref Vector4Pair other)
this.B += other.B;
}

/// <summary>
/// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4!
/// <summary>. Works only if Ve
JimBobSquarePants marked this conversation as resolved.
Show resolved Hide resolved
/// Downscale method, specific to Jpeg color conversctor{float}.Count == 4!
JimBobSquarePants marked this conversation as resolved.
Show resolved Hide resolved
/// TODO: Move it somewhere else.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RoundAndDownscalePreAvx2()
internal void RoundAndDownscalePreAvx2(float downscaleFactor)
{
ref Vector<float> a = ref Unsafe.As<Vector4, Vector<float>>(ref this.A);
a = a.FastRound();

ref Vector<float> b = ref Unsafe.As<Vector4, Vector<float>>(ref this.B);
b = b.FastRound();

// Downscale by 1/255
var scale = new Vector4(1 / 255f);
// Downscale by 1/factor
var scale = new Vector4(1 / downscaleFactor);
this.A *= scale;
this.B *= scale;
}
Expand All @@ -61,14 +61,14 @@ internal void RoundAndDownscalePreAvx2()
/// TODO: Move it somewhere else.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RoundAndDownscaleAvx2()
internal void RoundAndDownscaleAvx2(float downscaleFactor)
{
ref Vector<float> self = ref Unsafe.As<Vector4Pair, Vector<float>>(ref this);
Vector<float> v = self;
v = v.FastRound();

// Downscale by 1/255
v *= new Vector<float>(1 / 255f);
// Downscale by 1/factor
v *= new Vector<float>(1 / downscaleFactor);
self = v;
}

Expand Down
19 changes: 10 additions & 9 deletions src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Numerics;
using System.Runtime.CompilerServices;

Expand All @@ -9,10 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal partial struct Block8x8F
{
private static readonly Vector4 CMin4 = new Vector4(0F);
private static readonly Vector4 CMax4 = new Vector4(255F);
private static readonly Vector4 COff4 = new Vector4(128F);

/// <summary>
/// Transpose the block into the destination block.
/// </summary>
Expand Down Expand Up @@ -94,10 +91,14 @@ public void TransposeInto(ref Block8x8F d)
}

/// <summary>
/// Level shift by +128, clip to [0, 255]
/// Level shift by +maximum/2, clip to [0, maximum]
/// </summary>
public void NormalizeColorsInplace()
public void NormalizeColorsInplace(float maximum)
{
Vector4 CMin4 = new Vector4(0F);
Vector4 CMax4 = new Vector4(maximum);
Vector4 COff4 = new Vector4((float)Math.Ceiling(maximum/2));

this.V0L = Vector4.Clamp(this.V0L + COff4, CMin4, CMax4);
this.V0R = Vector4.Clamp(this.V0R + COff4, CMin4, CMax4);
this.V1L = Vector4.Clamp(this.V1L + COff4, CMin4, CMax4);
Expand All @@ -120,10 +121,10 @@ public void NormalizeColorsInplace()
/// AVX2-only variant for executing <see cref="NormalizeColorsInplace"/> and <see cref="RoundInplace"/> in one step.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void NormalizeColorsAndRoundInplaceAvx2()
public void NormalizeColorsAndRoundInplaceAvx2(float maximum)
{
Vector<float> off = new Vector<float>(128f);
Vector<float> max = new Vector<float>(255F);
Vector<float> off = new Vector<float>((float)Math.Ceiling(maximum/2));
Vector<float> max = new Vector<float>(maximum);

ref Vector<float> row0 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V0L);
row0 = NormalizeAndRound(row0, off, max);
Expand Down
19 changes: 10 additions & 9 deletions src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Numerics;
using System.Runtime.CompilerServices;

Expand All @@ -22,10 +23,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal partial struct Block8x8F
{
private static readonly Vector4 CMin4 = new Vector4(0F);
private static readonly Vector4 CMax4 = new Vector4(255F);
private static readonly Vector4 COff4 = new Vector4(128F);

/// <summary>
/// Transpose the block into the destination block.
/// </summary>
Expand Down Expand Up @@ -59,10 +56,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}

/// <summary>
/// Level shift by +128, clip to [0, 255]
/// Level shift by +maximum/2, clip to [0, maximum]
/// </summary>
public void NormalizeColorsInplace()
public void NormalizeColorsInplace(float maximum)
{
Vector4 CMin4 = new Vector4(0F);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These values are being calculated more than once in the same partial struct.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What we can do is to refactor the normalization logic into it's own stateful type (preferably a struct), where minimum/maximum/half values are nonstatic members initialized in the constructor.

Vector4 CMax4 = new Vector4(maximum);
Vector4 COff4 = new Vector4((float)Math.Ceiling(maximum/2));

<#

PushIndent(" ");
Expand All @@ -83,10 +84,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// AVX2-only variant for executing <see cref="NormalizeColorsInplace"/> and <see cref="RoundInplace"/> in one step.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void NormalizeColorsAndRoundInplaceAvx2()
public void NormalizeColorsAndRoundInplaceAvx2(float maximum)
{
Vector<float> off = new Vector<float>(128f);
Vector<float> max = new Vector<float>(255F);
Vector<float> off = new Vector<float>((float)Math.Ceiling(maximum/2));
Vector<float> max = new Vector<float>(maximum);
<#

for (int i = 0; i < 8; i++)
Expand Down
8 changes: 4 additions & 4 deletions src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
Original file line number Diff line number Diff line change
Expand Up @@ -467,17 +467,17 @@ public Block8x8 RoundAsInt16Block()
}

/// <summary>
/// Level shift by +128, clip to [0..255], and round all the values in the block.
/// Level shift by +maximum/2, clip to [0..maximum], and round all the values in the block.
/// </summary>
public void NormalizeColorsAndRoundInplace()
public void NormalizeColorsAndRoundInplace(float maximum)
{
if (SimdUtils.IsAvx2CompatibleArchitecture)
{
this.NormalizeColorsAndRoundInplaceAvx2();
this.NormalizeColorsAndRoundInplaceAvx2(maximum);
}
else
{
this.NormalizeColorsInplace();
this.NormalizeColorsInplace(maximum);
this.RoundInplace();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ internal abstract partial class JpegColorConverter
{
internal class FromCmyk : JpegColorConverter
{
public FromCmyk()
: base(JpegColorSpace.Cmyk)
public FromCmyk(int precision)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to have more type safety in the codebase. (-1, 17 and 222 are invalid values for precision ;) ). I suggest an enum having the following form, so you can easily cast it to int when necessary.

enum JpegPrecision
{
   EightBits = 8,
   TwelveBits = 12
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... Read section 4.7 here. Lossless jpeg supports additional precision so we should bear that in mind when naming our enum. https://www.w3.org/Graphics/JPEG/itu-t81.pdf

For DCT-based processes, two alternative sample precisions are specified: either 8 bits or 12 bits per sample. Applications which use samples with other precisions can use either 8-bit or 12-bit precision by shifting their source image samples appropriately. The baseline process uses only 8-bit precision. DCT-based implementations which handle 12-bit source image samples are likely to need greater computational resources than those which handle only 8-bit source images. Consequently in this Specification separate normative requirements are defined for 8-bit and 12-bit DCT-based processes.

For lossless processes the sample precision is specified to be from 2 to 16 bits

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. The options I see:

  1. Keep the type as is (int), and introduce some DebugGuard checks in the code
  2. Introduce an enum defining all entries for 2-16
  3. Since we do not support lossless jpeg, define only EightBits and TwelveBits for now, and YAGNI out the rest, letting our future selves to find out how to manage the lossless case.

I'm pretty much for option 3.

: base(JpegColorSpace.Cmyk, precision)
{
}

Expand All @@ -25,14 +25,17 @@ public override void ConvertToRgba(in ComponentValues values, Span<Vector4> resu

var v = new Vector4(0, 0, 0, 1F);

var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
var scale = new Vector4(1 / this.MaximumValue,
1 / this.MaximumValue,
1 / this.MaximumValue,
1F);

for (int i = 0; i < result.Length; i++)
{
float c = cVals[i];
float m = mVals[i];
float y = yVals[i];
float k = kVals[i] / 255F;
float k = kVals[i] / this.MaximumValue;

v.X = c * k;
v.Y = m * k;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ internal abstract partial class JpegColorConverter
{
internal class FromGrayscale : JpegColorConverter
{
public FromGrayscale()
: base(JpegColorSpace.Grayscale)
public FromGrayscale(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}

public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
var scale = new Vector4(1 / this.MaximumValue,
1 / this.MaximumValue,
1 / this.MaximumValue,
1F);

ref float sBase = ref MemoryMarshal.GetReference(values.Component0);
ref Vector4 dBase = ref MemoryMarshal.GetReference(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ internal abstract partial class JpegColorConverter
{
internal class FromRgb : JpegColorConverter
{
public FromRgb()
: base(JpegColorSpace.RGB)
public FromRgb(int precision)
: base(JpegColorSpace.RGB, precision)
{
}

Expand All @@ -24,7 +24,10 @@ public override void ConvertToRgba(in ComponentValues values, Span<Vector4> resu

var v = new Vector4(0, 0, 0, 1);

var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
var scale = new Vector4(1 / this.MaximumValue,
1 / this.MaximumValue,
1 / this.MaximumValue,
1F);

for (int i = 0; i < result.Length; i++)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ internal abstract partial class JpegColorConverter
{
internal class FromYCbCrBasic : JpegColorConverter
{
public FromYCbCrBasic()
: base(JpegColorSpace.YCbCr)
public FromYCbCrBasic(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}

public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
ConvertCore(values, result);
ConvertCore(values, result, this.MaximumValue, this.HalfValue);
}

internal static void ConvertCore(in ComponentValues values, Span<Vector4> result)
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue, float halfValue)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> yVals = values.Component0;
Expand All @@ -29,13 +29,13 @@ internal static void ConvertCore(in ComponentValues values, Span<Vector4> result

var v = new Vector4(0, 0, 0, 1);

var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
var scale = new Vector4(1 / maxValue, 1 / maxValue, 1 / maxValue, 1F);

for (int i = 0; i < result.Length; i++)
{
float y = yVals[i];
float cb = cbVals[i] - 128F;
float cr = crVals[i] - 128F;
float cb = cbVals[i] - halfValue;
float cr = crVals[i] - halfValue;

v.X = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero);
v.Y = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ internal abstract partial class JpegColorConverter
{
internal class FromYCbCrSimd : JpegColorConverter
{
public FromYCbCrSimd()
: base(JpegColorSpace.YCbCr)
public FromYCbCrSimd(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}

Expand All @@ -25,16 +25,17 @@ public override void ConvertToRgba(in ComponentValues values, Span<Vector4> resu
int simdCount = result.Length - remainder;
if (simdCount > 0)
{
ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount));
ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount), this.MaximumValue, this.HalfValue);
}

FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder));
FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder),
this.MaximumValue, this.HalfValue);
}

/// <summary>
/// SIMD convert using buffers of sizes divisible by 8.
/// </summary>
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result)
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue, float halfValue)
{
DebugGuard.IsTrue(result.Length % 8 == 0, nameof(result), "result.Length should be divisible by 8!");

Expand All @@ -48,7 +49,7 @@ internal static void ConvertCore(in ComponentValues values, Span<Vector4> result
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));

var chromaOffset = new Vector4(-128f);
var chromaOffset = new Vector4(-halfValue);

// Walking 8 elements at one step:
int n = result.Length / 8;
Expand All @@ -58,11 +59,11 @@ internal static void ConvertCore(in ComponentValues values, Span<Vector4> result
// y = yVals[i];
Vector4Pair y = Unsafe.Add(ref yBase, i);

// cb = cbVals[i] - 128F;
// cb = cbVals[i] - halfValue);
Vector4Pair cb = Unsafe.Add(ref cbBase, i);
cb.AddInplace(chromaOffset);

// cr = crVals[i] - 128F;
// cr = crVals[i] - halfValue;
Vector4Pair cr = Unsafe.Add(ref crBase, i);
cr.AddInplace(chromaOffset);

Expand Down Expand Up @@ -90,15 +91,15 @@ internal static void ConvertCore(in ComponentValues values, Span<Vector4> result
if (Vector<float>.Count == 4)
{
// TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector<T> is terrible?)
r.RoundAndDownscalePreAvx2();
g.RoundAndDownscalePreAvx2();
b.RoundAndDownscalePreAvx2();
r.RoundAndDownscalePreAvx2(maxValue);
g.RoundAndDownscalePreAvx2(maxValue);
b.RoundAndDownscalePreAvx2(maxValue);
}
else if (SimdUtils.IsAvx2CompatibleArchitecture)
{
r.RoundAndDownscaleAvx2();
g.RoundAndDownscaleAvx2();
b.RoundAndDownscaleAvx2();
r.RoundAndDownscaleAvx2(maxValue);
g.RoundAndDownscaleAvx2(maxValue);
b.RoundAndDownscaleAvx2(maxValue);
}
else
{
Expand Down
Loading