diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs index b301e8320..e1dcad1b6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs @@ -92,6 +92,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder return tables; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateY(byte r, byte g, byte b) + { + return (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateCb(byte r, byte g, byte b) + { + return (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateCr(byte r, byte g, byte b) + { + return (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits; + } + + /// /// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values. /// @@ -115,33 +134,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder crResult[i] = (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ConvertPixelInto( - int r, - int g, - int b, - ref Block8x8F yResult, - int i) - { - // float y = (0.299F * r) + (0.587F * g) + (0.114F * b); - yResult[i] = (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ConvertPixelInto(int r, int g, int b, ref float yResult) => - // float y = (0.299F * r) + (0.587F * g) + (0.114F * b); - yResult = (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ConvertPixelInto(int r, int g, int b, ref float cbResult, ref float crResult) - { - // float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); - cbResult = (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits; - - // float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); - crResult = (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits; - } - /// /// Converts Rgb24 pixels into YCbCr color space with 4:4:4 subsampling sampling of luminance and chroma. /// @@ -187,7 +179,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // 0-31 or 32-63 // upper or lower part - int chromaWriteOffset = row * Block8x8F.Size / 2; + int chromaWriteOffset = row * (Block8x8F.Size / 2); ref float cbBlockRef = ref Unsafe.Add(ref Unsafe.As(ref cbBlock), chromaWriteOffset); ref float crBlockRef = ref Unsafe.Add(ref Unsafe.As(ref crBlock), chromaWriteOffset); @@ -195,51 +187,72 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder for (int i = 0; i < 8; i += 2) { - // 8 pixels by 3 integers - Span rgbTriplets = stackalloc int[24]; - - for (int j = 0; j < 2; j++) - { - int yBlockWriteOffset = (i + j) * 8; - ref Rgb24 stride = ref Unsafe.Add(ref rgbStart, (i + j) * 16); - - // left - this.ConvertChunk420(ref stride, ref Unsafe.Add(ref yBlockLeftRef, yBlockWriteOffset), rgbTriplets); - - // right - this.ConvertChunk420(ref Unsafe.Add(ref stride, 8), ref Unsafe.Add(ref yBlockRightRef, yBlockWriteOffset), rgbTriplets.Slice(12)); - } - - int writeIdx = 8 * (i / 2); - ref float cbWriteRef = ref Unsafe.Add(ref cbBlockRef, writeIdx); - ref float crWriteRef = ref Unsafe.Add(ref crBlockRef, writeIdx); - for (int j = 0; j < 8; j++) - { - int idx = j * 3; - this.ConvertPixelInto( - rgbTriplets[idx] / 4, // r - rgbTriplets[idx + 1] / 4, // g - rgbTriplets[idx + 2] / 4, // b - ref Unsafe.Add(ref cbWriteRef, j), - ref Unsafe.Add(ref crWriteRef, j)); - } + int yBlockWriteOffset = i * 8; + ref Rgb24 stride = ref Unsafe.Add(ref rgbStart, i * 16); + + int chromaOffset = 8 * (i / 2); + + // left + this.ConvertChunk420( + ref stride, + ref Unsafe.Add(ref yBlockLeftRef, yBlockWriteOffset), + ref Unsafe.Add(ref cbBlockRef, chromaOffset), + ref Unsafe.Add(ref crBlockRef, chromaOffset)); + + // right + this.ConvertChunk420( + ref Unsafe.Add(ref stride, 8), + ref Unsafe.Add(ref yBlockRightRef, yBlockWriteOffset), + ref Unsafe.Add(ref cbBlockRef, chromaOffset + 4), + ref Unsafe.Add(ref crBlockRef, chromaOffset + 4)); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ConvertChunk420(ref Rgb24 stride, ref float yBlock, Span chromaRgbTriplet) + private void ConvertChunk420(ref Rgb24 stride, ref float yBlock, ref float cbBlock, ref float crBlock) { - for (int i = 0; i < 8; i++) + // jpeg 8x8 blocks are processed as 16x16 blocks with 16x8 subpasses (this is done for performance reasons) + // each row is 16 pixels wide thus +16 stride reference offset + // resulting luminance (Y`) are sampled at original resolution thus +8 reference offset + for (int k = 0; k < 8; k += 2) { - Rgb24 px0 = Unsafe.Add(ref stride, i); + ref float yBlockRef = ref Unsafe.Add(ref yBlock, k); + + // top row + Rgb24 px0 = Unsafe.Add(ref stride, k); + Rgb24 px1 = Unsafe.Add(ref stride, k + 1); + this.ConvertPixelInto(px0.R, px0.G, px0.B, ref yBlockRef); + this.ConvertPixelInto(px1.R, px1.G, px1.B, ref Unsafe.Add(ref yBlockRef, 1)); + + // bottom row + Rgb24 px2 = Unsafe.Add(ref stride, k + 16); + Rgb24 px3 = Unsafe.Add(ref stride, k + 17); + this.ConvertPixelInto(px2.R, px2.G, px2.B, ref Unsafe.Add(ref yBlockRef, 8)); + this.ConvertPixelInto(px3.R, px3.G, px3.B, ref Unsafe.Add(ref yBlockRef, 9)); + + Unsafe.Add(ref cbBlock, k / 2) = this.CalculateAverageCb(px0, px1, px2, px3); + Unsafe.Add(ref crBlock, k / 2) = this.CalculateAverageCr(px0, px1, px2, px3); + } + } - this.ConvertPixelInto(px0.R, px0.G, px0.B, ref Unsafe.Add(ref yBlock, i)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateAverageCb(Rgb24 px0, Rgb24 px1, Rgb24 px2, Rgb24 px3) + { + return 0.25f + * (this.CalculateCb(px0.R, px0.G, px0.B) + + this.CalculateCb(px1.R, px1.G, px1.B) + + this.CalculateCb(px2.R, px2.G, px2.B) + + this.CalculateCb(px3.R, px3.G, px3.B)); + } - int idx = 3 * (i / 2); - chromaRgbTriplet[idx] += px0.R; - chromaRgbTriplet[idx + 1] += px0.G; - chromaRgbTriplet[idx + 2] += px0.B; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateAverageCr(Rgb24 px0, Rgb24 px1, Rgb24 px2, Rgb24 px3) + { + return 0.25f + * (this.CalculateCr(px0.R, px0.G, px0.B) + + this.CalculateCr(px1.R, px1.G, px1.B) + + this.CalculateCr(px2.R, px2.G, px2.B) + + this.CalculateCr(px3.R, px3.G, px3.B)); } [MethodImpl(MethodImplOptions.AggressiveInlining)]