/// <summary>
        /// Creates a new memory group, allocating it's buffers with the provided allocator.
        /// </summary>
        /// <param name="allocator">The <see cref="MemoryAllocator"/> to use.</param>
        /// <param name="totalLength">The total length of the buffer.</param>
        /// <param name="bufferAlignment">The expected alignment (eg. to make sure image rows fit into single buffers).</param>
        /// <param name="options">The <see cref="AllocationOptions"/>.</param>
        /// <returns>A new <see cref="MemoryGroup{T}"/>.</returns>
        /// <exception cref="InvalidMemoryOperationException">Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator.</exception>
        public static MemoryGroup <T> Allocate(
            MemoryAllocator allocator,
            long totalLength,
            int bufferAlignment,
            AllocationOptions options = AllocationOptions.None)
        {
            Guard.NotNull(allocator, nameof(allocator));
            Guard.MustBeGreaterThanOrEqualTo(totalLength, 0, nameof(totalLength));
            Guard.MustBeGreaterThanOrEqualTo(bufferAlignment, 0, nameof(bufferAlignment));

            int blockCapacityInElements = allocator.GetBufferCapacityInBytes() / ElementSize;

            if (bufferAlignment > blockCapacityInElements)
            {
                throw new InvalidMemoryOperationException(
                          $"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignment}.");
            }

            if (totalLength == 0)
            {
                var buffers0 = new IMemoryOwner <T>[1] {
                    allocator.Allocate <T>(0, options)
                };
                return(new Owned(buffers0, 0, 0, true));
            }

            int numberOfAlignedSegments = blockCapacityInElements / bufferAlignment;
            int bufferLength            = numberOfAlignedSegments * bufferAlignment;

            if (totalLength > 0 && totalLength < bufferLength)
            {
                bufferLength = (int)totalLength;
            }

            int  sizeOfLastBuffer = (int)(totalLength % bufferLength);
            long bufferCount      = totalLength / bufferLength;

            if (sizeOfLastBuffer == 0)
            {
                sizeOfLastBuffer = bufferLength;
            }
            else
            {
                bufferCount++;
            }

            var buffers = new IMemoryOwner <T> [bufferCount];

            for (int i = 0; i < buffers.Length - 1; i++)
            {
                buffers[i] = allocator.Allocate <T>(bufferLength, options);
            }

            if (bufferCount > 0)
            {
                buffers[buffers.Length - 1] = allocator.Allocate <T>(sizeOfLastBuffer, options);
            }

            return(new Owned(buffers, bufferLength, totalLength, true));
        }
        /// <summary>
        /// Allocates padded buffers. Generally used by encoder/decoders.
        /// </summary>
        /// <param name="memoryAllocator">The <see cref="MemoryAllocator"/>.</param>
        /// <param name="width">Pixel count in the row</param>
        /// <param name="pixelSizeInBytes">The pixel size in bytes, eg. 3 for RGB.</param>
        /// <param name="paddingInBytes">The padding.</param>
        /// <returns>A <see cref="IMemoryOwner{Byte}"/>.</returns>
        internal static IMemoryOwner <byte> AllocatePaddedPixelRowBuffer(
            this MemoryAllocator memoryAllocator,
            int width,
            int pixelSizeInBytes,
            int paddingInBytes)
        {
            int length = (width * pixelSizeInBytes) + paddingInBytes;

            return(memoryAllocator.Allocate <byte>(length));
        }
        public static Buffer2D <T> Allocate2D <T>(
            this MemoryAllocator memoryAllocator,
            int width,
            int height,
            AllocationOptions options = AllocationOptions.None)
            where T : struct
        {
            IMemoryOwner <T> buffer = memoryAllocator.Allocate <T>(width * height, options);
            var memorySource        = new MemorySource <T>(buffer, true);

            return(new Buffer2D <T>(memorySource, width, height));
        }
        /// <summary>
        /// Allocates a buffer of value type objects interpreted as a 2D region
        /// of <paramref name="width"/> x <paramref name="height"/> elements.
        /// </summary>
        /// <typeparam name="T">The type of buffer items to allocate.</typeparam>
        /// <param name="memoryAllocator">The memory allocator.</param>
        /// <param name="width">The buffer width.</param>
        /// <param name="height">The buffer height.</param>
        /// <param name="preferContiguosImageBuffers">A value indicating whether the allocated buffer should be contiguous, unless bigger than <see cref="int.MaxValue"/>.</param>
        /// <param name="options">The allocation options.</param>
        /// <returns>The <see cref="Buffer2D{T}"/>.</returns>
        public static Buffer2D <T> Allocate2D <T>(
            this MemoryAllocator memoryAllocator,
            int width,
            int height,
            bool preferContiguosImageBuffers,
            AllocationOptions options = AllocationOptions.None)
            where T : struct
        {
            long            groupLength = (long)width * height;
            MemoryGroup <T> memoryGroup;

            if (preferContiguosImageBuffers && groupLength < int.MaxValue)
            {
                IMemoryOwner <T> buffer = memoryAllocator.Allocate <T>((int)groupLength, options);
                memoryGroup = MemoryGroup <T> .CreateContiguous(buffer, false);
            }
            else
            {
                memoryGroup = memoryAllocator.AllocateGroup <T>(groupLength, width, options);
            }

            return(new Buffer2D <T>(memoryGroup, width, height));
        }