diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/YCbCrToRgbTables.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/YCbCrToRgbTables.cs
index 4195a34672..27324b5f68 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/YCbCrToRgbTables.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/YCbCrToRgbTables.cs
@@ -6,80 +6,94 @@
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
+ internal unsafe struct YCbCrToRgbTables
{
+ ///
+ /// The red red-chrominance table
+ ///
+ public fixed int CrRTable[256];
+
+ ///
+ /// The blue blue-chrominance table
+ ///
+ public fixed int CbBTable[256];
+
+ ///
+ /// The green red-chrominance table
+ ///
+ public fixed int CrGTable[256];
+
+ ///
+ /// The green blue-chrominance table
+ ///
+ public fixed int CbGTable[256];
+
// 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];
+ ///
+ /// Initializes the YCbCr tables
+ ///
+ /// The intialized
+ public static YCbCrToRgbTables Create()
+ {
+ YCbCrToRgbTables tables = default(YCbCrToRgbTables);
+
+ 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
+ tables.CrRTable[i] = RightShift((Fix(1.402F) * x) + Half);
+
+ // Cb=>B value is nearest int to 1.772 * x
+ tables.CbBTable[i] = RightShift((Fix(1.772F) * x) + Half);
- private static readonly int[] CbBTable = new int[256];
+ // Cr=>G value is scaled-up -0.714136286
+ tables.CrGTable[i] = (-Fix(0.714136286F)) * x;
- private static readonly int[] CrGTable = new int[256];
+ // Cb => G value is scaled - up - 0.344136286 * x
+ // We also add in Half so that need not do it in inner loop
+ tables.CbGTable[i] = ((-Fix(0.344136286F)) * x) + Half;
+ }
- private static readonly int[] CbGTable = new int[256];
+ return tables;
+ }
///
/// Optimized method to pack bytes to the image from the YCbCr color space.
///
/// The pixel format.
/// The packed pixel.
+ /// The reference to the tables instance.
/// 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)
+ public static void Pack(ref TPixel packed, YCbCrToRgbTables* tables, 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);
+ byte r = (byte)(y + tables->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);
+ byte g = (byte)(y + RightShift(tables->CbGTable[cb] + tables->CrGTable[cr])).Clamp(0, 255);
// float b = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero);
- byte b = (byte)(y + CbBTable[cb]).Clamp(0, 255);
+ byte b = (byte)(y + tables->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)
{
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs
index 55a61011cd..199aa52252 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs
@@ -11,33 +11,86 @@ namespace ImageSharp.Formats.Jpg
/// 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
+ internal unsafe struct RgbToYCbCrTables
{
- // Speediest right-shift on some machines and gives us enough accuracy at 4 decimal places.
- private const int ScaleBits = 16;
+ ///
+ /// The red luminance table
+ ///
+ public fixed int YRTable[256];
- private const int GYOffset = 256;
+ ///
+ /// The green luminance table
+ ///
+ public fixed int YGTable[256];
- private const int BYOffset = 2 * 256;
+ ///
+ /// The blue luminance table
+ ///
+ public fixed int YBTable[256];
- private const int RCbOffset = 3 * 256;
+ ///
+ /// The red blue-chrominance table
+ ///
+ public fixed int CbRTable[256];
- private const int GCbOffset = 4 * 256;
+ ///
+ /// The green blue-chrominance table
+ ///
+ public fixed int CbGTable[256];
- private const int BCbOffset = 5 * 256;
+ ///
+ /// The blue blue-chrominance table
+ /// B=>Cb and R=>Cr are the same
+ ///
+ public fixed int CbBTable[256];
- // B=>Cb and R=>Cr are the same
- private const int RCrOffset = BCbOffset;
+ ///
+ /// The green red-chrominance table
+ ///
+ public fixed int CrGTable[256];
- private const int GCrOffset = 6 * 256;
+ ///
+ /// The blue red-chrominance table
+ ///
+ public fixed int CrBTable[256];
- private const int BCrOffset = 7 * 256;
+ // Speediest right-shift on some machines and gives us enough accuracy at 4 decimal places.
+ private const int ScaleBits = 16;
private const int CBCrOffset = 128 << ScaleBits;
private const int Half = 1 << (ScaleBits - 1);
- private static readonly int[] YCbCrTable = new int[8 * 256];
+ ///
+ /// Initializes the YCbCr tables
+ ///
+ /// The intialized
+ public static RgbToYCbCrTables Create()
+ {
+ RgbToYCbCrTables tables = default(RgbToYCbCrTables);
+
+ for (int i = 0; i <= 255; i++)
+ {
+ // The values for the calculations are left scaled up since we must add them together before rounding.
+ tables.YRTable[i] = Fix(0.299F) * i;
+ tables.YGTable[i] = Fix(0.587F) * i;
+ tables.YBTable[i] = (Fix(0.114F) * i) + Half;
+ tables.CbRTable[i] = (-Fix(0.168735892F)) * i;
+ tables.CbGTable[i] = (-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
+ tables.CbBTable[i] = (Fix(0.5F) * i) + CBCrOffset + Half - 1;
+
+ tables.CrGTable[i] = (-Fix(0.418687589F)) * i;
+ tables.CrBTable[i] = (-Fix(0.081312411F)) * i;
+ }
+
+ return tables;
+ }
///
/// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values.
@@ -45,50 +98,22 @@ namespace ImageSharp.Formats.Jpg
/// The The luminance block.
/// The red chroma block.
/// The blue chroma block.
+ /// The reference to the tables instance.
/// 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)
+ public static void Allocate(ref float* yBlockRaw, ref float* cbBlockRaw, ref float* crBlockRaw, RgbToYCbCrTables* tables, 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;
+ yBlockRaw[index] = (tables->YRTable[r] + tables->YGTable[g] + tables->YBTable[b]) >> 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;
+ cbBlockRaw[index] = (tables->CbRTable[r] + tables->CbGTable[g] + tables->CbBTable[b]) >> 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;
+ crBlockRaw[index] = (tables->CbBTable[r] + tables->CrGTable[g] + tables->CrBTable[b]) >> ScaleBits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 3faffb566d..2072ec1e12 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -40,7 +40,7 @@ namespace ImageSharp.Formats
///
/// Lookup tables for converting YCbCr to Rgb
///
- private static readonly YCbCrToRgbTables YCbCrToRgbTables = default(YCbCrToRgbTables).Init();
+ private static YCbCrToRgbTables yCbCrToRgbTables = YCbCrToRgbTables.Create();
///
/// The decoder options.
@@ -685,19 +685,23 @@ namespace ImageSharp.Formats
image.Configuration.ParallelOptions,
y =>
{
- // TODO: Simplify + optimize + share duplicate code across converter methods
- int yo = this.ycbcrImage.GetRowYOffset(y);
- int co = this.ycbcrImage.GetRowCOffset(y);
-
- for (int x = 0; x < image.Width; x++)
+ // TODO. How can we use the fixed tables inside the lambda?
+ fixed (YCbCrToRgbTables* tables = &yCbCrToRgbTables)
{
- byte yy = this.ycbcrImage.YChannel.Pixels[yo + x];
- byte cb = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)];
- byte cr = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)];
-
- TPixel packed = default(TPixel);
- YCbCrToRgbTables.Pack(ref packed, yy, cb, cr);
- pixels[x, y] = packed;
+ // TODO: Simplify + optimize + share duplicate code across converter methods
+ int yo = this.ycbcrImage.GetRowYOffset(y);
+ int co = this.ycbcrImage.GetRowCOffset(y);
+
+ for (int x = 0; x < image.Width; x++)
+ {
+ byte yy = this.ycbcrImage.YChannel.Pixels[yo + x];
+ byte cb = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)];
+ byte cr = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)];
+
+ TPixel packed = default(TPixel);
+ YCbCrToRgbTables.Pack(ref packed, tables, yy, cb, cr);
+ pixels[x, y] = packed;
+ }
}
});
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index b741fb2e65..f88efb3d2b 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -22,11 +22,6 @@ 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.
///
@@ -106,6 +101,11 @@ namespace ImageSharp.Formats
}
};
+ ///
+ /// Lookup tables for converting Rgb to YCbCr
+ ///
+ private static RgbToYCbCrTables rgbToYCbCrTables = RgbToYCbCrTables.Create();
+
///
/// A scratch buffer to reduce allocations.
///
@@ -288,6 +288,7 @@ namespace ImageSharp.Formats
///
/// The pixel format.
/// The pixel accessor.
+ /// The reference to the tables instance.
/// The x-position within the image.
/// The y-position within the image.
/// The luminance block.
@@ -296,6 +297,7 @@ namespace ImageSharp.Formats
/// Temporal provided by the caller
private static void ToYCbCr(
PixelAccessor pixels,
+ RgbToYCbCrTables* tables,
int x,
int y,
Block8x8F* yBlock,
@@ -326,7 +328,7 @@ namespace ImageSharp.Formats
int index = j8 + i;
- RgbToYCbCrTables.Allocate(ref yBlockRaw, ref cbBlockRaw, ref crBlockRaw, index, r, g, b);
+ RgbToYCbCrTables.Allocate(ref yBlockRaw, ref cbBlockRaw, ref crBlockRaw, tables, index, r, g, b);
dataIdx += 3;
}
@@ -447,38 +449,41 @@ namespace ImageSharp.Formats
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
- using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.Xyz))
+ fixed (RgbToYCbCrTables* tables = &rgbToYCbCrTables)
{
- for (int y = 0; y < pixels.Height; y += 8)
+ using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.Xyz))
{
- for (int x = 0; x < pixels.Width; x += 8)
+ for (int y = 0; y < pixels.Height; y += 8)
{
- ToYCbCr(pixels, x, y, &b, &cb, &cr, rgbBytes);
-
- prevDCY = this.WriteBlock(
- QuantIndex.Luminance,
- prevDCY,
- &b,
- &temp1,
- &temp2,
- &onStackLuminanceQuantTable,
- unzig.Data);
- prevDCCb = this.WriteBlock(
- QuantIndex.Chrominance,
- prevDCCb,
- &cb,
- &temp1,
- &temp2,
- &onStackChrominanceQuantTable,
- unzig.Data);
- prevDCCr = this.WriteBlock(
- QuantIndex.Chrominance,
- prevDCCr,
- &cr,
- &temp1,
- &temp2,
- &onStackChrominanceQuantTable,
- unzig.Data);
+ for (int x = 0; x < pixels.Width; x += 8)
+ {
+ ToYCbCr(pixels, tables, x, y, &b, &cb, &cr, rgbBytes);
+
+ prevDCY = this.WriteBlock(
+ QuantIndex.Luminance,
+ prevDCY,
+ &b,
+ &temp1,
+ &temp2,
+ &onStackLuminanceQuantTable,
+ unzig.Data);
+ prevDCCb = this.WriteBlock(
+ QuantIndex.Chrominance,
+ prevDCCb,
+ &cb,
+ &temp1,
+ &temp2,
+ &onStackChrominanceQuantTable,
+ unzig.Data);
+ prevDCCr = this.WriteBlock(
+ QuantIndex.Chrominance,
+ prevDCCr,
+ &cr,
+ &temp1,
+ &temp2,
+ &onStackChrominanceQuantTable,
+ unzig.Data);
+ }
}
}
}
@@ -820,49 +825,51 @@ namespace ImageSharp.Formats
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
-
- using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.Xyz))
+ fixed (RgbToYCbCrTables* tables = &rgbToYCbCrTables)
{
- for (int y = 0; y < pixels.Height; y += 16)
+ using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.Xyz))
{
- for (int x = 0; x < pixels.Width; x += 16)
+ for (int y = 0; y < pixels.Height; y += 16)
{
- for (int i = 0; i < 4; i++)
+ for (int x = 0; x < pixels.Width; x += 16)
{
- int xOff = (i & 1) * 8;
- int yOff = (i & 2) * 4;
-
- ToYCbCr(pixels, x + xOff, y + yOff, &b, cbPtr + i, crPtr + i, rgbBytes);
+ for (int i = 0; i < 4; i++)
+ {
+ int xOff = (i & 1) * 8;
+ int yOff = (i & 2) * 4;
+
+ ToYCbCr(pixels, tables, x + xOff, y + yOff, &b, cbPtr + i, crPtr + i, rgbBytes);
+
+ prevDCY = this.WriteBlock(
+ QuantIndex.Luminance,
+ prevDCY,
+ &b,
+ &temp1,
+ &temp2,
+ &onStackLuminanceQuantTable,
+ unzig.Data);
+ }
+
+ Block8x8F.Scale16X16To8X8(&b, cbPtr);
+ prevDCCb = this.WriteBlock(
+ QuantIndex.Chrominance,
+ prevDCCb,
+ &b,
+ &temp1,
+ &temp2,
+ &onStackChrominanceQuantTable,
+ unzig.Data);
- prevDCY = this.WriteBlock(
- QuantIndex.Luminance,
- prevDCY,
+ Block8x8F.Scale16X16To8X8(&b, crPtr);
+ prevDCCr = this.WriteBlock(
+ QuantIndex.Chrominance,
+ prevDCCr,
&b,
&temp1,
&temp2,
- &onStackLuminanceQuantTable,
+ &onStackChrominanceQuantTable,
unzig.Data);
}
-
- Block8x8F.Scale16X16To8X8(&b, cbPtr);
- prevDCCb = this.WriteBlock(
- QuantIndex.Chrominance,
- prevDCCb,
- &b,
- &temp1,
- &temp2,
- &onStackChrominanceQuantTable,
- unzig.Data);
-
- Block8x8F.Scale16X16To8X8(&b, crPtr);
- prevDCCr = this.WriteBlock(
- QuantIndex.Chrominance,
- prevDCCr,
- &b,
- &temp1,
- &temp2,
- &onStackChrominanceQuantTable,
- unzig.Data);
}
}
}