diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/YCbCrToRgbTables.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/YCbCrToRgbTables.cs
new file mode 100644
index 000000000..4195a3467
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/YCbCrToRgbTables.cs
@@ -0,0 +1,95 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpg
+{
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.PixelFormats;
+
+ ///
+ /// Provides 8-bit lookup tables for converting from YCbCr to Rgb colorspace.
+ /// Methods to build the tables are based on libjpeg implementation.
+ ///
+ internal struct YCbCrToRgbTables
+ {
+ // Speediest right-shift on some machines and gives us enough accuracy at 4 decimal places.
+ private const int ScaleBits = 16;
+
+ private const int Half = 1 << (ScaleBits - 1);
+
+ private static readonly int[] CrRTable = new int[256];
+
+ private static readonly int[] CbBTable = new int[256];
+
+ private static readonly int[] CrGTable = new int[256];
+
+ private static readonly int[] CbGTable = new int[256];
+
+ ///
+ /// Optimized method to pack bytes to the image from the YCbCr color space.
+ ///
+ /// The pixel format.
+ /// The packed pixel.
+ /// The y luminance component.
+ /// The cb chroma component.
+ /// The cr chroma component.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Pack(ref TPixel packed, byte y, byte cb, byte cr)
+ where TPixel : struct, IPixel
+ {
+ // float r = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero);
+ byte r = (byte)(y + CrRTable[cr]).Clamp(0, 255);
+
+ // float g = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero);
+ // The values for the G calculation are left scaled up, since we must add them together before rounding.
+ byte g = (byte)(y + RightShift(CbGTable[cb] + CrGTable[cr])).Clamp(0, 255);
+
+ // float b = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero);
+ byte b = (byte)(y + CbBTable[cb]).Clamp(0, 255);
+
+ packed.PackFromBytes(r, g, b, byte.MaxValue);
+ }
+
+ ///
+ /// Initializes the YCbCr tables
+ ///
+ /// The intialized
+ public YCbCrToRgbTables Init()
+ {
+ for (int i = 0, x = -128; i <= 255; i++, x++)
+ {
+ // i is the actual input pixel value, in the range 0..255
+ // The Cb or Cr value we are thinking of is x = i - 128
+ // Cr=>R value is nearest int to 1.402 * x
+ CrRTable[i] = RightShift((Fix(1.402F) * x) + Half);
+
+ // Cb=>B value is nearest int to 1.772 * x
+ CbBTable[i] = RightShift((Fix(1.772F) * x) + Half);
+
+ // Cr=>G value is scaled-up -0.714136286
+ CrGTable[i] = (-Fix(0.714136286F)) * x;
+
+ // Cb => G value is scaled - up - 0.344136286 * x
+ // We also add in Half so that need not do it in inner loop
+ CbGTable[i] = ((-Fix(0.344136286F)) * x) + Half;
+ }
+
+ return this;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int Fix(float x)
+ {
+ return (int)((x * (1L << ScaleBits)) + 0.5F);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int RightShift(int x)
+ {
+ return x >> ScaleBits;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs
new file mode 100644
index 000000000..55a61011c
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs
@@ -0,0 +1,106 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpg
+{
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace.
+ /// Methods to build the tables are based on libjpeg implementation.
+ ///
+ internal struct RgbToYCbCrTables
+ {
+ // Speediest right-shift on some machines and gives us enough accuracy at 4 decimal places.
+ private const int ScaleBits = 16;
+
+ private const int GYOffset = 256;
+
+ private const int BYOffset = 2 * 256;
+
+ private const int RCbOffset = 3 * 256;
+
+ private const int GCbOffset = 4 * 256;
+
+ private const int BCbOffset = 5 * 256;
+
+ // B=>Cb and R=>Cr are the same
+ private const int RCrOffset = BCbOffset;
+
+ private const int GCrOffset = 6 * 256;
+
+ private const int BCrOffset = 7 * 256;
+
+ private const int CBCrOffset = 128 << ScaleBits;
+
+ private const int Half = 1 << (ScaleBits - 1);
+
+ private static readonly int[] YCbCrTable = new int[8 * 256];
+
+ ///
+ /// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values.
+ ///
+ /// The The luminance block.
+ /// The red chroma block.
+ /// The blue chroma block.
+ /// The current index.
+ /// The red value.
+ /// The green value.
+ /// The blue value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void Allocate(ref float* yBlockRaw, ref float* cbBlockRaw, ref float* crBlockRaw, int index, int r, int g, int b)
+ {
+ // float y = (0.299F * r) + (0.587F * g) + (0.114F * b);
+ yBlockRaw[index] = (YCbCrTable[r] + YCbCrTable[g + GYOffset] + YCbCrTable[b + BYOffset]) >> ScaleBits;
+
+ // float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
+ cbBlockRaw[index] = (YCbCrTable[r + RCbOffset] + YCbCrTable[g + GCbOffset] + YCbCrTable[b + BCbOffset]) >> ScaleBits;
+
+ // float b = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero);
+ crBlockRaw[index] = (YCbCrTable[r + RCrOffset] + YCbCrTable[g + GCrOffset] + YCbCrTable[b + BCrOffset]) >> ScaleBits;
+ }
+
+ ///
+ /// Initializes the YCbCr tables
+ ///
+ /// The intialized
+ public RgbToYCbCrTables Init()
+ {
+ for (int i = 0; i <= 255; i++)
+ {
+ // The values for the calculations are left scaled up since we must add them together before rounding.
+ YCbCrTable[i] = Fix(0.299F) * i;
+ YCbCrTable[i + GYOffset] = Fix(0.587F) * i;
+ YCbCrTable[i + BYOffset] = (Fix(0.114F) * i) + Half;
+ YCbCrTable[i + RCbOffset] = (-Fix(0.168735892F)) * i;
+ YCbCrTable[i + GCbOffset] = (-Fix(0.331264108F)) * i;
+
+ // We use a rounding fudge - factor of 0.5 - epsilon for Cb and Cr.
+ // This ensures that the maximum output will round to 255
+ // not 256, and thus that we don't have to range-limit.
+ //
+ // B=>Cb and R=>Cr tables are the same
+ YCbCrTable[i + BCbOffset] = (Fix(0.5F) * i) + CBCrOffset + Half - 1;
+
+ YCbCrTable[i + GCrOffset] = (-Fix(0.418687589F)) * i;
+ YCbCrTable[i + BCrOffset] = (-Fix(0.081312411F)) * i;
+ }
+
+ return this;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int Fix(float x)
+ {
+ return (int)((x * (1L << ScaleBits)) + 0.5F);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int RightShift(int x)
+ {
+ return x >> ScaleBits;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 186c1e528..3faffb566 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -7,7 +7,6 @@ namespace ImageSharp.Formats
{
using System;
using System.IO;
- using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using ImageSharp.Formats.Jpg;
@@ -38,6 +37,11 @@ namespace ImageSharp.Formats
public InputProcessor InputProcessor;
#pragma warning restore SA401
+ ///
+ /// Lookup tables for converting YCbCr to Rgb
+ ///
+ private static readonly YCbCrToRgbTables YCbCrToRgbTables = default(YCbCrToRgbTables).Init();
+
///
/// The decoder options.
///
@@ -251,35 +255,6 @@ namespace ImageSharp.Formats
}
}
- ///
- /// Optimized method to pack bytes to the image from the YCbCr color space.
- /// This is faster than implicit casting as it avoids double packing.
- ///
- /// The pixel format.
- /// The packed pixel.
- /// The y luminance component.
- /// The cb chroma component.
- /// The cr chroma component.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void PackYcbCr(ref TPixel packed, byte y, byte cb, byte cr)
- where TPixel : struct, IPixel
- {
- int ccb = cb - 128;
- int ccr = cr - 128;
-
- // Speed up the algorithm by removing floating point calculation
- // Scale by 65536, add .5F and truncate value. We use bit shifting to divide the result
- int r0 = 91881 * ccr; // (1.402F * 65536) + .5F
- int g0 = 22554 * ccb; // (0.34414F * 65536) + .5F
- int g1 = 46802 * ccr; // (0.71414F * 65536) + .5F
- int b0 = 116130 * ccb; // (1.772F * 65536) + .5F
-
- byte r = (byte)(y + (r0 >> 16)).Clamp(0, 255);
- byte g = (byte)(y - (g0 >> 16) - (g1 >> 16)).Clamp(0, 255);
- byte b = (byte)(y + (b0 >> 16)).Clamp(0, 255);
- packed.PackFromBytes(r, g, b, 255);
- }
-
///
/// Read metadata from stream and read the blocks in the scans into .
///
@@ -721,7 +696,7 @@ namespace ImageSharp.Formats
byte cr = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)];
TPixel packed = default(TPixel);
- PackYcbCr(ref packed, yy, cb, cr);
+ YCbCrToRgbTables.Pack(ref packed, yy, cb, cr);
pixels[x, y] = packed;
}
});
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index eb083c35d..b741fb2e6 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -5,11 +5,9 @@
namespace ImageSharp.Formats
{
- using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
-
using ImageSharp.Formats.Jpg;
using ImageSharp.Formats.Jpg.Components;
using ImageSharp.PixelFormats;
@@ -24,6 +22,11 @@ namespace ImageSharp.Formats
///
private const int QuantizationTableCount = 2;
+ ///
+ /// Lookup tables for converting Rgb to YCbCr
+ ///
+ private static readonly RgbToYCbCrTables RgbToYCbCrTables = default(RgbToYCbCrTables).Init();
+
///
/// Counts the number of bits needed to hold an integer.
///
@@ -321,29 +324,9 @@ namespace ImageSharp.Formats
int g = Unsafe.Add(ref data0, dataIdx + 1);
int b = Unsafe.Add(ref data0, dataIdx + 2);
- // Speed up the algorithm by removing floating point calculation
- // Scale by 65536, add .5F and truncate value. We use bit shifting to divide the result
- int y0 = 19595 * r; // (0.299F * 65536) + .5F
- int y1 = 38470 * g; // (0.587F * 65536) + .5F
- int y2 = 7471 * b; // (0.114F * 65536) + .5F
-
- int cb0 = -11057 * r; // (-0.168736F * 65536) + .5F
- int cb1 = 21710 * g; // (0.331264F * 65536) + .5F
- int cb2 = 32768 * b; // (0.5F * 65536) + .5F
-
- int cr0 = 32768 * r; // (0.5F * 65536) + .5F
- int cr1 = 27439 * g; // (0.418688F * 65536) + .5F
- int cr2 = 5329 * b; // (0.081312F * 65536) + .5F
-
- float yy = (y0 + y1 + y2) >> 16;
- float cb = 128 + ((cb0 - cb1 + cb2) >> 16);
- float cr = 128 + ((cr0 - cr1 - cr2) >> 16);
-
int index = j8 + i;
- yBlockRaw[index] = yy;
- cbBlockRaw[index] = cb;
- crBlockRaw[index] = cr;
+ RgbToYCbCrTables.Allocate(ref yBlockRaw, ref cbBlockRaw, ref crBlockRaw, index, r, g, b);
dataIdx += 3;
}