From 45dfed1411f258c72feaa30049f51e1c176f5112 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 7 May 2022 01:46:21 +0300 Subject: [PATCH] RGB debug pass encoding done --- .../Jpeg/Components/Block8x8F.ScaledCopyTo.cs | 32 +++++++ .../Formats/Jpeg/Components/Block8x8F.cs | 13 +++ .../JpegColorConverter.FromYCbCrAvx.cs | 5 ++ .../ColorConverters/JpegColorConverterBase.cs | 21 +++++ .../Components/Encoder/HuffmanScanEncoder.cs | 6 ++ .../Jpeg/Components/Encoder/JpegComponent.cs | 4 +- .../Encoder/JpegComponentPostProcessor.cs | 80 +++++++++++++++++- .../Jpeg/Components/Encoder/JpegFrame.cs | 4 +- .../Encoder/SpectralConverter{TPixel}.cs | 83 ++++++++----------- .../Formats/Jpeg/JpegEncoderCore.cs | 40 ++++----- .../PixelFormats/PixelOperations{TPixel}.cs | 37 +++++++++ 11 files changed, 250 insertions(+), 75 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs index 498fe4d03b..ae2d1f722c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; @@ -21,6 +22,37 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components this.ScaledCopyTo(ref areaOrigin, region.Stride, horizontalScale, verticalScale); } + [MethodImpl(InliningOptions.ShortMethod)] + public void ScaledCopyFrom(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + { + if (horizontalScale == 1 && verticalScale == 1) + { + ref byte selfBase = ref Unsafe.As(ref this); + ref byte destBase = ref Unsafe.As(ref areaOrigin); + int destStride = areaStride * sizeof(float); + + CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 0); + CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 1); + CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 2); + CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 3); + CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 4); + CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 5); + CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 6); + CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 7); + return; + } + + throw new NotImplementedException("This is a test setup!"); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void CopyRowImplFromStrides(ref byte src, ref byte dst, int srcStride, int row) + { + ref byte s = ref Unsafe.Add(ref dst, row * srcStride); + ref byte d = ref Unsafe.Add(ref src, row * 8 * sizeof(float)); + Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); + } + } + [MethodImpl(InliningOptions.ShortMethod)] public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index d7511fddac..0190fc7454 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -352,6 +352,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } + public void DE_NormalizeColors(float maximum) + { + if (SimdUtils.HasVector8) + { + this.NormalizeColorsAndRoundInPlaceVector8(maximum); + } + else + { + this.NormalizeColorsInPlace(maximum); + this.RoundInPlace(); + } + } + /// /// Rounds all values in the block. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs index 892bcc79e1..53fa176480 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs @@ -68,6 +68,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters c2 = b; } } + + public override void ConvertFromRgbInplace(in ComponentValues values) + { + return; + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs index 808ca687b4..464358d0e8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs @@ -79,6 +79,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// The input/ouptut as a stack-only struct public abstract void ConvertToRgbInplace(in ComponentValues values); + public virtual void ConvertFromRgbInplace(in ComponentValues values) => throw new NotImplementedException("This is a test exception"); + /// /// Returns the s for all supported colorspaces and precisions. /// @@ -233,6 +235,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : Span.Empty; } + /// + /// Initializes a new instance of the struct. + /// + /// List of component color processors. + /// Row to convert + public ComponentValues(IReadOnlyList processors, int row) + { + DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); + + this.ComponentCount = processors.Count; + + this.Component0 = processors[0].GetColorBufferRowSpan(row); + + // In case of grayscale, Component1 and Component2 point to Component0 memory area + this.Component1 = this.ComponentCount > 1 ? processors[1].GetColorBufferRowSpan(row) : this.Component0; + this.Component2 = this.ComponentCount > 2 ? processors[2].GetColorBufferRowSpan(row) : this.Component0; + this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : Span.Empty; + } + internal ComponentValues( int componentCount, Span c0, diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 7c683af2b3..ece305a5aa 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -132,6 +132,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder where TPixel : unmanaged, IPixel { // DEBUG INITIALIZATION SETUP + this.huffmanTables = HuffmanLut.TheHuffmanLut; + var frame = new JpegFrame(configuration.MemoryAllocator, image, componentCount: 3); frame.Init(1, 1); frame.AllocateComponents(fullScan: false); @@ -188,6 +190,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // After all interleaved components, that's an interleaved MCU mcu++; + if (this.IsStreamFlushNeeded) + { + this.FlushToStream(); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs index 3293f55d1e..f9c7eb6349 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs @@ -67,12 +67,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// Gets or sets the index for the DC Huffman table. /// - public int DcTableId { get; set; } + public int DcTableId { get; set; } = 0; // TODO: DEBUG!!! /// /// Gets or sets the index for the AC Huffman table. /// - public int AcTableId { get; set; } + public int AcTableId { get; set; } = 1; // TODO: DEBUG!!! /// public void Dispose() diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs index dd9d485a84..8c6008620b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs @@ -8,18 +8,90 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { internal class JpegComponentPostProcessor : IDisposable { + private readonly Size blockAreaSize; + private readonly JpegComponent component; - private readonly Block8x8F dequantTable; + private readonly int blockRowsPerStep; + + private Block8x8F quantTable; - public JpegComponentPostProcessor(JpegComponent component, Block8x8F dequantTable) + public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegComponent component, Size postProcessorBufferSize, Block8x8F quantTable) { this.component = component; - this.dequantTable = dequantTable; + this.quantTable = quantTable; + FastFloatingPointDCT.AdjustToFDCT(ref this.quantTable); + + this.component = component; + this.blockAreaSize = this.component.SubSamplingDivisors * 8; + this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( + postProcessorBufferSize.Width, + postProcessorBufferSize.Height, + this.blockAreaSize.Height); + + this.blockRowsPerStep = postProcessorBufferSize.Height / 8 / this.component.SubSamplingDivisors.Height; } - public void Dispose() + /// + /// Gets the temporary working buffer of color values. + /// + public Buffer2D ColorBuffer { get; } + + public void CopyColorBufferToBlocks(int spectralStep) { + Buffer2D spectralBuffer = this.component.SpectralBlocks; + + // should be this.frame.MaxColorChannelValue + // but currently 12-bit jpegs are not supported + float maximumValue = 255f; + float normalizationValue = -128f; + + int blocksRowsPerStep = this.component.SamplingFactors.Height; + + int destAreaStride = this.ColorBuffer.Width; + + int yBlockStart = spectralStep * this.blockRowsPerStep; + + Size subSamplingDivisors = this.component.SubSamplingDivisors; + + Block8x8F workspaceBlock = default; + + for (int y = 0; y < this.blockRowsPerStep; y++) + { + int yBuffer = y * this.blockAreaSize.Height; + + for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) + { + Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); + Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); + + // load 8x8 block from 8 pixel strides + int xColorBufferStart = xBlock * this.blockAreaSize.Width; + workspaceBlock.ScaledCopyFrom( + ref colorBufferRow[xColorBufferStart], + destAreaStride, + subSamplingDivisors.Width, + subSamplingDivisors.Height); + + // multiply by maximum (for debug only, should be done in color converter) + workspaceBlock.MultiplyInPlace(maximumValue); + + // level shift via -128f + workspaceBlock.AddInPlace(normalizationValue); + + // FDCT + FastFloatingPointDCT.TransformFDCT(ref workspaceBlock); + + // Quantize and save to spectral blocks + Block8x8F.Quantize(ref workspaceBlock, ref blockRow[xBlock], ref this.quantTable); + } + } } + + public Span GetColorBufferRowSpan(int row) + => this.ColorBuffer.DangerousGetRowSpan(row); + + public void Dispose() + => this.ColorBuffer.Dispose(); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs index 585e3bafaa..3343ae02ba 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs @@ -24,8 +24,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.Components = new JpegComponent[] { new JpegComponent(allocator, 1, 1, 0), - new JpegComponent(allocator, 1, 1, 1), - new JpegComponent(allocator, 1, 1, 1), + new JpegComponent(allocator, 1, 1, 0), + new JpegComponent(allocator, 1, 1, 0), }; } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs index 1065e69c56..fe9c31b093 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -24,10 +24,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder private IMemoryOwner rgbBuffer; - private IMemoryOwner paddedProxyPixelRow; - private Buffer2D pixelBuffer; + private Decoder.ColorConverters.JpegColorConverterBase colorConverter; + public SpectralConverter(Configuration configuration) => this.configuration = configuration; @@ -46,9 +46,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // currently codec only supports encoding single frame jpegs this.pixelBuffer = image.GetRootFramePixelBuffer(); - // ??? - //this.paddedProxyPixelRow = allocator.Allocate(frame.PixelWidth + 3); - // component processors from spectral to Rgba32 const int blockPixelWidth = 8; var postProcessorBufferSize = new Size(majorBlockWidth * blockPixelWidth, this.pixelRowsPerStep); @@ -56,14 +53,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder for (int i = 0; i < this.componentProcessors.Length; i++) { JpegComponent component = frame.Components[i]; - this.componentProcessors[i] = new JpegComponentPostProcessor(component, dequantTables[component.QuantizationTableIndex]); + this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, component, postProcessorBufferSize, dequantTables[component.QuantizationTableIndex]); } // single 'stride' rgba32 buffer for conversion between spectral and TPixel - //this.rgbBuffer = allocator.Allocate(frame.PixelWidth * 3); + this.rgbBuffer = allocator.Allocate(frame.PixelWidth * 3); - // color converter from Rgba32 to TPixel - //this.colorConverter = this.GetColorConverter(frame, jpegData); + // color converter from Rgb24 to YCbCr + this.colorConverter = Decoder.ColorConverters.JpegColorConverterBase.GetConverter(colorSpace: Decoder.JpegColorSpace.YCbCr, precision: 8); } public void ConvertStrideBaseline() @@ -83,44 +80,36 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); - //for (int i = 0; i < this.componentProcessors.Length; i++) - //{ - // this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep); - //} - - //int width = this.pixelBuffer.Width; - - //for (int yy = this.pixelRowCounter; yy < maxY; yy++) - //{ - // int y = yy - this.pixelRowCounter; - - // var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); - - // this.colorConverter.ConvertToRgbInplace(values); - // values = values.Slice(0, width); // slice away Jpeg padding - - // Span r = this.rgbBuffer.Slice(0, width); - // Span g = this.rgbBuffer.Slice(width, width); - // Span b = this.rgbBuffer.Slice(width * 2, width); - - // SimdUtils.NormalizedFloatToByteSaturate(values.Component0, r); - // SimdUtils.NormalizedFloatToByteSaturate(values.Component1, g); - // SimdUtils.NormalizedFloatToByteSaturate(values.Component2, b); - - // // PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row. - // // If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row, - // // pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row. - // if (this.pixelBuffer.DangerousTryGetPaddedRowSpan(yy, 3, out Span destRow)) - // { - // PixelOperations.Instance.PackFromRgbPlanes(this.configuration, r, g, b, destRow); - // } - // else - // { - // Span proxyRow = this.paddedProxyPixelRow.GetSpan(); - // PixelOperations.Instance.PackFromRgbPlanes(this.configuration, r, g, b, proxyRow); - // proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.DangerousGetRowSpan(yy)); - // } - //} + int width = this.pixelBuffer.Width; + + // unpack TPixel to r/g/b planes + Span r = this.rgbBuffer.Slice(0, width); + Span g = this.rgbBuffer.Slice(width, width); + Span b = this.rgbBuffer.Slice(width * 2, width); + + for (int yy = this.pixelRowCounter; yy < maxY; yy++) + { + int y = yy - this.pixelRowCounter; + + // PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row. + // If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row, + // pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row. + Span sourceRow = this.pixelBuffer.DangerousGetRowSpan(yy); + PixelOperations.Instance.UnpackIntoRgbPlanes(this.configuration, r, g, b, sourceRow); + + var values = new Decoder.ColorConverters.JpegColorConverterBase.ComponentValues(this.componentProcessors, y); + + SimdUtils.ByteToNormalizedFloat(r, values.Component0); + SimdUtils.ByteToNormalizedFloat(g, values.Component1); + SimdUtils.ByteToNormalizedFloat(b, values.Component2); + + this.colorConverter.ConvertFromRgbInplace(values); + } + + for (int i = 0; i < this.componentProcessors.Length; i++) + { + this.componentProcessors[i].CopyColorBufferToBlocks(spectralStep); + } this.pixelRowCounter += this.pixelRowsPerStep; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index c20babd3d5..7d80278291 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -131,28 +131,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Write the scan header. this.WriteStartOfScan(componentCount, componentIds); - var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; - new HuffmanScanEncoder(3, stream).Encode(image, quantTables, Configuration.Default, cancellationToken); + //var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; + //new HuffmanScanEncoder(3, stream).Encode(image, quantTables, Configuration.Default, cancellationToken); // Write the scan compressed data. - //switch (this.colorType) - //{ - // case JpegColorType.YCbCrRatio444: - // new HuffmanScanEncoder(3, stream).Encode444(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken); - // break; - // case JpegColorType.YCbCrRatio420: - // new HuffmanScanEncoder(6, stream).Encode420(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken); - // break; - // case JpegColorType.Luminance: - // new HuffmanScanEncoder(1, stream).EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken); - // break; - // case JpegColorType.Rgb: - // new HuffmanScanEncoder(3, stream).EncodeRgb(image, ref luminanceQuantTable, cancellationToken); - // break; - // default: - // // all other non-supported color types are checked at the start of this method - // break; - //} + switch (this.colorType) + { + case JpegColorType.YCbCrRatio444: + new HuffmanScanEncoder(3, stream).Encode444(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken); + break; + case JpegColorType.YCbCrRatio420: + new HuffmanScanEncoder(6, stream).Encode420(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken); + break; + case JpegColorType.Luminance: + new HuffmanScanEncoder(1, stream).EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken); + break; + case JpegColorType.Rgb: + new HuffmanScanEncoder(3, stream).EncodeRgb(image, ref luminanceQuantTable, cancellationToken); + break; + default: + // all other non-supported color types are checked at the start of this method + break; + } // Write the End Of Image marker. this.WriteEndOfImageMarker(); diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index 710eb9c083..701d63b55f 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -198,6 +198,43 @@ namespace SixLabors.ImageSharp.PixelFormats } } + /// + /// Bulk operation that unpacks pixels from + /// into 3 seperate RGB channels. The destination must have a padding of 3. + /// + /// A to configure internal operations. + /// A to the red values. + /// A to the green values. + /// A to the blue values. + /// A to the destination pixels. + internal virtual void UnpackIntoRgbPlanes( + Configuration configuration, + ReadOnlySpan redChannel, + ReadOnlySpan greenChannel, + ReadOnlySpan blueChannel, + Span source) + { + Guard.NotNull(configuration, nameof(configuration)); + + int count = redChannel.Length; + + Rgba32 rgba32 = default; + ref byte r = ref MemoryMarshal.GetReference(redChannel); + ref byte g = ref MemoryMarshal.GetReference(greenChannel); + ref byte b = ref MemoryMarshal.GetReference(blueChannel); + ref TPixel d = ref MemoryMarshal.GetReference(source); + + for (int i = 0; i < count; i++) + { + // TODO: Create ToRgb24 method in IPixel + // TODO: create a fast intrinsic accelerated Rgb24 -> r/g/b planes overload + Unsafe.Add(ref d, i).ToRgba32(ref rgba32); + Unsafe.Add(ref r, i) = rgba32.R; + Unsafe.Add(ref g, i) = rgba32.G; + Unsafe.Add(ref b, i) = rgba32.B; + } + } + [MethodImpl(InliningOptions.ShortMethod)] internal static void GuardPackFromRgbPlanes(ReadOnlySpan greenChannel, ReadOnlySpan blueChannel, Span destination, int count) {