/// <summary>
        /// HSL to RGB image
        /// </summary>
        /// <param name="source">image source</param>
        /// <param name="dest">image destination</param>
        public void HslToRgb(IImage2DFloatRgbA source, IImage2DFloatRgbA dest)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (dest == null) throw new ArgumentNullException("dest");
            if ((source.Width > dest.Width) || (source.Height > dest.Height)) throw new ArgumentException("Destination image (" + dest.Width + "x" + dest.Height + ") must have at least the same size as the source image (" + source.Width + "x" + source.Height + ")");

            if (source.Normalized)
            {
                // use normalized kernel
                // TODO: HSL to RGB image (normalized)
            }
            else
            {
                // use normal kernel
                // TODO: HSL to RGB image
            }
            dest.Normalized = source.Normalized;
        }
        /// <summary>
        /// RGB to HSL image
        /// </summary>
        /// <param name="source">image source</param>
        /// <param name="dest">image destination</param>
        public void RgbToHsl(IImage2DFloatRgbA source, IImage2DFloatRgbA dest)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (dest == null) throw new ArgumentNullException("dest");
            if ((source.Width > dest.Width) || (source.Height > dest.Height)) throw new ArgumentException("Destination image (" + dest.Width + "x" + dest.Height + ") must have at least the same size as the source image (" + source.Width + "x" + source.Height + ")");

            if (source.Normalized)
            {
                // use normalized kernel
                for (int i = 0, j = 0, length = source.HostBuffer.Length; i < length;)
                {
                    Color color = Color.FromArgb((int)(source.HostBuffer[i++] * 255f), (int)(source.HostBuffer[i++] * 255f), (int)(source.HostBuffer[i++] * 255f));
                    dest.HostBuffer[j++] = color.GetHue() / 360f;
                    dest.HostBuffer[j++] = color.GetSaturation();
                    dest.HostBuffer[j++] = color.GetBrightness();
                    dest.HostBuffer[j++] = source.HostBuffer[i++];
                }
            }
            else
            {
                // use normal kernel
                for (int i = 0, j = 0, length = source.HostBuffer.Length; i < length; )
                {
                    Color color = Color.FromArgb((int)source.HostBuffer[i++], (int)source.HostBuffer[i++], (int)source.HostBuffer[i++]);
                    dest.HostBuffer[j++] = color.GetHue() * 255f / 360f;
                    dest.HostBuffer[j++] = color.GetSaturation() * 255f;
                    dest.HostBuffer[j++] = color.GetBrightness() * 255f;
                    dest.HostBuffer[j++] = source.HostBuffer[i++];
                }
            }
            dest.Normalized = source.Normalized;
        }
        /// <summary>
        /// GrayScale image
        /// </summary>
        /// <param name="source">image source</param>
        /// <param name="dest">image destination</param>
        public void GrayScale(IImage2DFloatRgbA source, IImage2DFloatA dest)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (dest == null) throw new ArgumentNullException("dest");
            if ((source.Width > dest.Width) || (source.Height > dest.Height)) throw new ArgumentException("Destination image (" + dest.Width + "x" + dest.Height + ") must have at least the same size as the source image (" + source.Width + "x" + source.Height + ")");

            int length = source.Width * source.Height;
            int pos = 0;
            for (int i = 0; i < length; i++)
            {
                float gray = 0.2989f * source.HostBuffer[pos++] + 0.5870f * source.HostBuffer[pos++] + 0.1140f * source.HostBuffer[pos++];
                pos++;
                dest.HostBuffer[i] = gray;
            }
            dest.Normalized = source.Normalized;
        }
        /// <summary>
        /// Flip image Y coordinate
        /// </summary>
        /// <param name="source">image source</param>
        /// <param name="dest">image destination</param>
        public void FlipY(IImage2DFloatRgbA source, IImage2DFloatRgbA dest)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (dest == null) throw new ArgumentNullException("dest");
            if (source == dest) throw new ArgumentException("Flipping kernel is not designed to run inline therefore source and destination must be different images");
            if ((source.Width > dest.Width) || (source.Height > dest.Height)) throw new ArgumentException("Destination image (" + dest.Width + "x" + dest.Height + ") must have at least the same size as the source image (" + source.Width + "x" + source.Height + ")");

            var sBuf = source.HostBuffer;
            var dBuf = dest.HostBuffer;

            int posSource = 0;
            int posDest = 4 * (source.Height - 1) * dest.Width;

            for (int y = 0, h = source.Height; y < h; y++)
            {
                for (int x = 0, w = source.Width; x < w; x++)
                {
                    float color;
                    color = sBuf[posSource++]; dBuf[posDest++] = color;
                    color = sBuf[posSource++]; dBuf[posDest++] = color;
                    color = sBuf[posSource++]; dBuf[posDest++] = color;
                    color = sBuf[posSource++]; dBuf[posDest++] = color;
                }
                posDest -= 4 * (dest.Width + source.Width);
            }
        }
        /// <summary>
        /// Extracts a channel of an RGB image
        /// </summary>
        /// <param name="source">image source</param>
        /// <param name="dest">image destination</param>
        /// <param name="offset">offset (0..3)</param>
        public void ExtractChannel(IImage2DFloatRgbA source, IImage2DFloatA dest, byte offset)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (dest == null) throw new ArgumentNullException("dest");
            if (offset > 3) throw new ArgumentOutOfRangeException("offset", String.Format("offset must be between 0..3 but was {0}", offset));
            if ((source.Width > dest.Width) || (source.Height > dest.Height)) throw new ArgumentException("Destination image (" + dest.Width + "x" + dest.Height + ") must have at least the same size as the source image (" + source.Width + "x" + source.Height + ")");

            int length = source.Width * source.Height;
            int pos = 0;
            for (int i = 0; i < length; i++)
                for (int j = 0; j < 4; j++)
                    if (j == offset) dest.HostBuffer[i] = source.HostBuffer[pos++]; else pos++;

            dest.Normalized = source.Normalized;
        }
        /// <summary>
        /// Box blur image
        /// </summary>
        /// <param name="source">image source</param>
        /// <param name="dest">image destination</param>
        /// <param name="sampler">sampler to be used for image reading</param>
        /// <param name="offset">offset</param>
        public void BoxBlur(IImage2DFloatRgbA source, IImage2DFloatRgbA dest, int offset)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (dest == null) throw new ArgumentNullException("dest");
            if ((source.Width > dest.Width) || (source.Height > dest.Height)) throw new ArgumentException("Destination image (" + dest.Width + "x" + dest.Height + ") must have at least the same size as the source image (" + source.Width + "x" + source.Height + ")");

            // TODO: Box blur image
            dest.Normalized = source.Normalized;
        }
        /// <summary>
        /// Sets channel of a RGBA image
        /// </summary>
        /// <param name="source">source image</param>
        /// <param name="mask">mask image</param>
        /// <param name="dest">destination image</param>
        /// <param name="offset">offset (0..3)</param>
        public void SetChannel(IImage2DFloatRgbA source, IImage2DFloatA mask, IImage2DFloatRgbA dest, byte offset)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (mask == null) throw new ArgumentNullException("mask");
            if (dest == null) throw new ArgumentNullException("dest");
            if (offset > 3) throw new ArgumentOutOfRangeException("offset", String.Format("offset must be between 0..3 but was {0}", offset));

            if ((source.Width > mask.Width) || (source.Height > mask.Height)) throw new ArgumentException("Image mask (" + dest.Width + "x" + dest.Height + ") must have at least the same size as the source image (" + source.Width + "x" + source.Height + ")");
            if ((source.Width > dest.Width) || (source.Height > dest.Height)) throw new ArgumentException("Destination image (" + dest.Width + "x" + dest.Height + ") must have at least the same size as the source image (" + source.Width + "x" + source.Height + ")");

            int length = source.Width * source.Height;
            int pos = 0;
            for (int i = 0; i < length; i++)
                for (int j = 0; j < 4; j++)
                {
                    dest.HostBuffer[pos] = (j == offset) ? mask.HostBuffer[i] : source.HostBuffer[pos];
                    pos++;
                }
        }