From bd363b50fc2482aa74b466a597bfcf4e4e8ebb81 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 26 Apr 2017 15:31:05 +1000 Subject: [PATCH 1/4] Use same conversion methods as libjpeg --- .../Components/Decoder/YCbCrToRgbTables.cs | 95 ++++++++++++++++ .../Components/Encoder/RgbToYCbCrTables.cs | 106 ++++++++++++++++++ .../Formats/Jpeg/JpegDecoderCore.cs | 37 +----- .../Formats/Jpeg/JpegEncoderCore.cs | 29 +---- 4 files changed, 213 insertions(+), 54 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/YCbCrToRgbTables.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs 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 0000000000..4195a34672 --- /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 0000000000..55a61011cd --- /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 186c1e5282..3faffb566d 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 eb083c35d9..b741fb2e65 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; } From 38e4fcbdfb460792799f43794558d5fde4c69588 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 26 Apr 2017 21:51:11 +1000 Subject: [PATCH 2/4] Use better structs with fixed pointer --- .../Components/Decoder/YCbCrToRgbTables.cs | 88 ++++++----- .../Components/Encoder/RgbToYCbCrTables.cs | 117 ++++++++------ .../Formats/Jpeg/JpegDecoderCore.cs | 30 ++-- .../Formats/Jpeg/JpegEncoderCore.cs | 143 +++++++++--------- 4 files changed, 214 insertions(+), 164 deletions(-) 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); } } } From 287bec4f434b06388def7c3c216651a189370a3b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 26 Apr 2017 22:45:54 +1000 Subject: [PATCH 3/4] Fix tests? --- tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs b/tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs index bcbc27c7c4..24850954a8 100644 --- a/tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs +++ b/tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs @@ -36,13 +36,13 @@ namespace ImageSharp.Tests.Colors [Fact] public void Multiply() { - Assert.Equal(RgbaVector.Multiply(Backdrop, RgbaVector.Black).ToVector4(), Rgba32.Black.ToVector4(), FloatComparer); + Assert.Equal(RgbaVector.Multiply(Backdrop, RgbaVector.Black).ToVector4(), RgbaVector.Black.ToVector4(), FloatComparer); Assert.Equal(RgbaVector.Multiply(Backdrop, RgbaVector.White).ToVector4(), Backdrop.ToVector4(), FloatComparer); RgbaVector multiply = RgbaVector.Multiply(Backdrop, Source); Assert.Equal(multiply.ToVector4(), new RgbaVector(0, 41, 0).ToVector4(), FloatComparer); } - + [Fact] public void Screen() { From e29a0dde9441b9719581779f47d928947e76c7df Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 27 Apr 2017 01:03:24 +1000 Subject: [PATCH 4/4] Squeeze out a couple of milliseconds. Seeing sub 30ms in benchmark for the first time ever. --- .../Components/Encoder/RgbToYCbCrTables.cs | 2 +- .../Formats/Jpeg/JpegDecoderCore.cs | 53 ++++++++++--------- .../Formats/Jpeg/JpegEncoderCore.cs | 2 +- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs index 199aa52252..94173d3e43 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs @@ -104,7 +104,7 @@ namespace ImageSharp.Formats.Jpg /// The green value. /// The blue value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Allocate(ref float* yBlockRaw, ref float* cbBlockRaw, ref float* crBlockRaw, RgbToYCbCrTables* tables, int index, int r, int g, int b) + public static void Allocate(ref float* yBlockRaw, ref float* cbBlockRaw, ref float* crBlockRaw, ref RgbToYCbCrTables* tables, int index, int r, int g, int b) { // float y = (0.299F * r) + (0.587F * g) + (0.114F * b); yBlockRaw[index] = (tables->YRTable[r] + tables->YGTable[g] + tables->YBTable[b]) >> ScaleBits; diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 2072ec1e12..9df21a3b72 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Formats { using System; using System.IO; + using System.Runtime.CompilerServices; using System.Threading.Tasks; using ImageSharp.Formats.Jpg; @@ -680,30 +681,34 @@ namespace ImageSharp.Formats using (PixelAccessor pixels = image.Lock()) { Parallel.For( - 0, - image.Height, - image.Configuration.ParallelOptions, - y => - { - // TODO. How can we use the fixed tables inside the lambda? - fixed (YCbCrToRgbTables* tables = &yCbCrToRgbTables) - { - // 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; - } - } - }); + 0, + image.Height, + image.Configuration.ParallelOptions, + y => + { + // TODO. This Parallel loop doesn't give us the boost it should. + ref byte ycRef = ref this.ycbcrImage.YChannel.Pixels[0]; + ref byte cbRef = ref this.ycbcrImage.CbChannel.Pixels[0]; + ref byte crRef = ref this.ycbcrImage.CrChannel.Pixels[0]; + fixed (YCbCrToRgbTables* tables = &yCbCrToRgbTables) + { + // 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++) + { + int cOff = co + (x / scale); + byte yy = Unsafe.Add(ref ycRef, yo + x); + byte cb = Unsafe.Add(ref cbRef, cOff); + byte cr = Unsafe.Add(ref crRef, cOff); + + TPixel packed = default(TPixel); + YCbCrToRgbTables.Pack(ref packed, tables, yy, cb, cr); + pixels[x, y] = packed; + } + } + }); } this.AssignResolution(image); diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index f88efb3d2b..0ce59c6dec 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -328,7 +328,7 @@ namespace ImageSharp.Formats int index = j8 + i; - RgbToYCbCrTables.Allocate(ref yBlockRaw, ref cbBlockRaw, ref crBlockRaw, tables, index, r, g, b); + RgbToYCbCrTables.Allocate(ref yBlockRaw, ref cbBlockRaw, ref crBlockRaw, ref tables, index, r, g, b); dataIdx += 3; }