Ejemplo n.º 1
0
        /// <summary>
        /// This function reads the output probabilities from finalLayer to CPU, sorts them and gets the label with heighest probability
        /// </summary>
        /// <param name="finalLayer">output image of the network this has probabilities of each digit</param>
        /// <returns>Guess of the network as to what the digit is as uint</returns>
        public uint GetLabel(MPSImage finalLayer)
        {
            // even though we have 10 labels outputed the MTLTexture format used is RGBAFloat16 thus 3 slices will have 3*4 = 12 outputs
            var resultHalfArray       = Enumerable.Repeat((ushort)6, 12).ToArray();
            var resultHalfArrayHandle = GCHandle.Alloc(resultHalfArray, GCHandleType.Pinned);
            var resultHalfArrayPtr    = resultHalfArrayHandle.AddrOfPinnedObject();

            var resultFloatArray       = Enumerable.Repeat(0.3f, 10).ToArray();
            var resultFloatArrayHandle = GCHandle.Alloc(resultFloatArray, GCHandleType.Pinned);
            var resultFloatArrayPtr    = resultFloatArrayHandle.AddrOfPinnedObject();

            for (uint i = 0; i <= 2; i++)
            {
                finalLayer.Texture.GetBytes(resultHalfArrayPtr + 4 * (int)i * sizeof(ushort),
                                            sizeof(ushort) * 1 * 4, sizeof(ushort) * 1 * 1 * 4,
                                            new MTLRegion(new MTLOrigin(0, 0, 0), new MTLSize(1, 1, 1)),
                                            0, i);
            }

            // we use vImage to convert our data to float16, Metal GPUs use float16 and swift float is 32-bit
            var fullResultVImagebuf = new vImageBuffer {
                Data        = resultFloatArrayPtr,
                Height      = 1,
                Width       = 10,
                BytesPerRow = 10 * 4
            };

            var halfResultVImagebuf = new vImageBuffer {
                Data        = resultHalfArrayPtr,
                Height      = 1,
                Width       = 10,
                BytesPerRow = 10 * 2
            };

            if (Planar16FtoPlanarF(ref halfResultVImagebuf, ref fullResultVImagebuf, 0) != vImageError.NoError)
            {
                Console.WriteLine("Error in vImage");
            }

            // poll all labels for probability and choose the one with max probability to return
            float max = 0f;
            uint  mostProbableDigit = 10;

            for (uint i = 0; i <= 9; i++)
            {
                if (max < resultFloatArray [i])
                {
                    max = resultFloatArray [i];
                    mostProbableDigit = i;
                }
            }

            resultHalfArrayHandle.Free();
            resultFloatArrayHandle.Free();

            return(mostProbableDigit);
        }
Ejemplo n.º 2
0
        public unsafe static UIImage ApplyBlur(UIImage image, float blurRadius, UIColor tintColor, float saturationDeltaFactor, UIImage maskImage)
        {
            if (image.Size.Width < 1 || image.Size.Height < 1)
            {
                Debug.WriteLine(@"*** error: invalid size: ({0} x {1}). Both dimensions must be >= 1: {2}", image.Size.Width, image.Size.Height, image);
                return(null);
            }
            if (image.CGImage == null)
            {
                Debug.WriteLine(@"*** error: image must be backed by a CGImage: {0}", image);
                return(null);
            }
            if (maskImage != null && maskImage.CGImage == null)
            {
                Debug.WriteLine(@"*** error: maskImage must be backed by a CGImage: {0}", maskImage);
                return(null);
            }

            var imageRect   = new RectangleF(PointF.Empty, image.Size);
            var effectImage = image;

            bool hasBlur             = blurRadius > float.Epsilon;
            bool hasSaturationChange = Math.Abs(saturationDeltaFactor - 1) > float.Epsilon;

            if (hasBlur || hasSaturationChange)
            {
                UIGraphics.BeginImageContextWithOptions(image.Size, false, UIScreen.MainScreen.Scale);
                var contextIn = UIGraphics.GetCurrentContext();
                contextIn.ScaleCTM(1.0f, -1.0f);
                contextIn.TranslateCTM(0, -image.Size.Height);
                contextIn.DrawImage(imageRect, image.CGImage);
                var effectInContext = contextIn.AsBitmapContext() as CGBitmapContext;

                var effectInBuffer = new vImageBuffer()
                {
                    Data        = effectInContext.Data,
                    Width       = effectInContext.Width,
                    Height      = effectInContext.Height,
                    BytesPerRow = effectInContext.BytesPerRow
                };

                UIGraphics.BeginImageContextWithOptions(image.Size, false, UIScreen.MainScreen.Scale);
                var effectOutContext = UIGraphics.GetCurrentContext().AsBitmapContext() as CGBitmapContext;
                var effectOutBuffer  = new vImageBuffer()
                {
                    Data        = effectOutContext.Data,
                    Width       = effectOutContext.Width,
                    Height      = effectOutContext.Height,
                    BytesPerRow = effectOutContext.BytesPerRow
                };

                if (hasBlur)
                {
                    var  inputRadius = blurRadius * UIScreen.MainScreen.Scale;
                    uint radius      = (uint)(Math.Floor(inputRadius * 3 * Math.Sqrt(2 * Math.PI) / 4 + 0.5));
                    if ((radius % 2) != 1)
                    {
                        radius += 1;
                    }
                    vImage.BoxConvolveARGB8888(ref effectInBuffer, ref effectOutBuffer, IntPtr.Zero, 0, 0, radius, radius, Pixel8888.Zero, vImageFlags.EdgeExtend);
                    vImage.BoxConvolveARGB8888(ref effectOutBuffer, ref effectInBuffer, IntPtr.Zero, 0, 0, radius, radius, Pixel8888.Zero, vImageFlags.EdgeExtend);
                    vImage.BoxConvolveARGB8888(ref effectInBuffer, ref effectOutBuffer, IntPtr.Zero, 0, 0, radius, radius, Pixel8888.Zero, vImageFlags.EdgeExtend);
                }
                bool effectImageBuffersAreSwapped = false;
                if (hasSaturationChange)
                {
                    var s = saturationDeltaFactor;
                    var floatingPointSaturationMatrix = new float [] {
                        0.0722f + 0.9278f * s, 0.0722f - 0.0722f * s, 0.0722f - 0.0722f * s, 0,
                        0.7152f - 0.7152f * s, 0.7152f + 0.2848f * s, 0.7152f - 0.7152f * s, 0,
                        0.2126f - 0.2126f * s, 0.2126f - 0.2126f * s, 0.2126f + 0.7873f * s, 0,
                        0, 0, 0, 1,
                    };
                    const int divisor          = 256;
                    var       saturationMatrix = new short [floatingPointSaturationMatrix.Length];
                    for (int i = 0; i < saturationMatrix.Length; i++)
                    {
                        saturationMatrix [i] = (short)Math.Round(floatingPointSaturationMatrix [i] * divisor);
                    }
                    if (hasBlur)
                    {
                        vImage.MatrixMultiplyARGB8888(ref effectOutBuffer, ref effectInBuffer, saturationMatrix, divisor, null, null, vImageFlags.NoFlags);
                        effectImageBuffersAreSwapped = true;
                    }
                    else
                    {
                        vImage.MatrixMultiplyARGB8888(ref effectInBuffer, ref effectOutBuffer, saturationMatrix, divisor, null, null, vImageFlags.NoFlags);
                    }
                }
                if (!effectImageBuffersAreSwapped)
                {
                    effectImage = UIGraphics.GetImageFromCurrentImageContext();
                }
                UIGraphics.EndImageContext();
                if (effectImageBuffersAreSwapped)
                {
                    effectImage = UIGraphics.GetImageFromCurrentImageContext();
                }
                UIGraphics.EndImageContext();
            }

            // Setup up output context
            UIGraphics.BeginImageContextWithOptions(image.Size, false, UIScreen.MainScreen.Scale);
            var outputContext = UIGraphics.GetCurrentContext();

            outputContext.ScaleCTM(1, -1);
            outputContext.TranslateCTM(0, -image.Size.Height);

            // Draw base image
            if (hasBlur)
            {
                outputContext.SaveState();
                if (maskImage != null)
                {
                    outputContext.ClipToMask(imageRect, maskImage.CGImage);
                }
                outputContext.DrawImage(imageRect, effectImage.CGImage);
                outputContext.RestoreState();
            }

            if (tintColor != null)
            {
                outputContext.SaveState();
                outputContext.SetFillColor(tintColor.CGColor);
                outputContext.FillRect(imageRect);
                outputContext.RestoreState();
            }
            var outputImage = UIGraphics.GetImageFromCurrentImageContext();

            UIGraphics.EndImageContext();
            return(outputImage);
        }
Ejemplo n.º 3
0
        public unsafe static UIImage ApplyBlur (UIImage image, float blurRadius, UIColor tintColor, float saturationDeltaFactor, UIImage maskImage)
        {
            if (image.Size.Width < 1 || image.Size.Height < 1) {
                Debug.WriteLine (@"*** error: invalid size: ({0} x {1}). Both dimensions must be >= 1: {2}", image.Size.Width, image.Size.Height, image);
                return null;
            }
            if (image.CGImage == null) {
                Debug.WriteLine (@"*** error: image must be backed by a CGImage: {0}", image);
                return null;
            }
            if (maskImage != null && maskImage.CGImage == null) {
                Debug.WriteLine (@"*** error: maskImage must be backed by a CGImage: {0}", maskImage);
                return null;
            }

            var imageRect = new RectangleF (PointF.Empty, image.Size);
            var effectImage = image;

            bool hasBlur = blurRadius > float.Epsilon;
            bool hasSaturationChange = Math.Abs (saturationDeltaFactor - 1) > float.Epsilon;

            if (hasBlur || hasSaturationChange) {
                UIGraphics.BeginImageContextWithOptions (image.Size, false, UIScreen.MainScreen.Scale);
                var contextIn = UIGraphics.GetCurrentContext ();
                contextIn.ScaleCTM (1.0f, -1.0f);
                contextIn.TranslateCTM (0, -image.Size.Height);
                contextIn.DrawImage (imageRect, image.CGImage);
                var effectInContext = contextIn.AsBitmapContext () as CGBitmapContext;

                var effectInBuffer = new vImageBuffer () {
                    Data = effectInContext.Data,
                    Width = effectInContext.Width,
                    Height = effectInContext.Height,
                    BytesPerRow = effectInContext.BytesPerRow
                };

                UIGraphics.BeginImageContextWithOptions (image.Size, false, UIScreen.MainScreen.Scale);
                var effectOutContext = UIGraphics.GetCurrentContext ().AsBitmapContext () as CGBitmapContext;               
                var effectOutBuffer = new vImageBuffer () {
                    Data = effectOutContext.Data,
                    Width = effectOutContext.Width,
                    Height = effectOutContext.Height,
                    BytesPerRow = effectOutContext.BytesPerRow
                };

                if (hasBlur) {
                    var inputRadius = blurRadius * UIScreen.MainScreen.Scale;
                    uint radius = (uint)(Math.Floor (inputRadius * 3 * Math.Sqrt (2 * Math.PI) / 4 + 0.5));
                    if ((radius % 2) != 1)
                        radius += 1;
                    vImage.BoxConvolveARGB8888 (ref effectInBuffer, ref effectOutBuffer, IntPtr.Zero, 0, 0, radius, radius, Pixel8888.Zero, vImageFlags.EdgeExtend);
                    vImage.BoxConvolveARGB8888 (ref effectOutBuffer, ref effectInBuffer, IntPtr.Zero, 0, 0, radius, radius, Pixel8888.Zero, vImageFlags.EdgeExtend);
                    vImage.BoxConvolveARGB8888 (ref effectInBuffer, ref effectOutBuffer, IntPtr.Zero, 0, 0, radius, radius, Pixel8888.Zero, vImageFlags.EdgeExtend);
                }
                bool effectImageBuffersAreSwapped = false;
                if (hasSaturationChange) {
                    var s = saturationDeltaFactor;
                    var floatingPointSaturationMatrix = new float [] {
                        0.0722f + 0.9278f * s,  0.0722f - 0.0722f * s,  0.0722f - 0.0722f * s,  0,
                        0.7152f - 0.7152f * s,  0.7152f + 0.2848f * s,  0.7152f - 0.7152f * s,  0,
                        0.2126f - 0.2126f * s,  0.2126f - 0.2126f * s,  0.2126f + 0.7873f * s,  0,
                        0,                    0,                    0,  1,
                    };
                    const int divisor = 256;
                    var saturationMatrix = new short [floatingPointSaturationMatrix.Length];
                    for (int i = 0; i < saturationMatrix.Length; i++)
                        saturationMatrix [i] = (short)Math.Round (floatingPointSaturationMatrix [i] * divisor);
                    if (hasBlur) {
                        vImage.MatrixMultiplyARGB8888 (ref effectOutBuffer, ref effectInBuffer, saturationMatrix, divisor, null, null, vImageFlags.NoFlags);
                        effectImageBuffersAreSwapped = true;
                    } else
                        vImage.MatrixMultiplyARGB8888 (ref effectInBuffer, ref effectOutBuffer, saturationMatrix, divisor, null, null, vImageFlags.NoFlags);
                }
                if (!effectImageBuffersAreSwapped)
                    effectImage = UIGraphics.GetImageFromCurrentImageContext ();
                UIGraphics.EndImageContext ();
                if (effectImageBuffersAreSwapped)
                    effectImage = UIGraphics.GetImageFromCurrentImageContext ();
                UIGraphics.EndImageContext ();
            }

            // Setup up output context
            UIGraphics.BeginImageContextWithOptions (image.Size, false, UIScreen.MainScreen.Scale);
            var outputContext = UIGraphics.GetCurrentContext ();
            outputContext.ScaleCTM (1, -1);
            outputContext.TranslateCTM (0, -image.Size.Height);

            // Draw base image
            if (hasBlur) {
                outputContext.SaveState ();
                if (maskImage != null)
                    outputContext.ClipToMask (imageRect, maskImage.CGImage);
                outputContext.DrawImage (imageRect, effectImage.CGImage);
                outputContext.RestoreState ();
            }

            if (tintColor != null) {
                outputContext.SaveState ();
                outputContext.SetFillColor (tintColor.CGColor);
                outputContext.FillRect (imageRect);
                outputContext.RestoreState ();
            }
            var outputImage = UIGraphics.GetImageFromCurrentImageContext ();
            UIGraphics.EndImageContext ();
            return outputImage;
        }
Ejemplo n.º 4
0
 unsafe public static vImageError Planar16FtoPlanarF(ref vImageBuffer src, ref vImageBuffer dest, vImageFlags flags)
 {
     return((vImageError)(long)vImageConvert_Planar16FtoPlanarF(ref src, ref dest, flags));
 }
Ejemplo n.º 5
0
 extern static nint vImageConvert_Planar16FtoPlanarF(ref vImageBuffer src, ref vImageBuffer dest, vImageFlags flags);
Ejemplo n.º 6
0
        /// <summary>
        /// Internal method to create a blurred image since this has to run on the main thread.
        /// </summary>
        /// <returns>The blurred image.</returns>
        /// <param name="image">Image to be blurred.</param>
        /// <param name="blurRadius">Blur radius.</param>
        /// <remarks>
        /// Originally from: https://github.com/xamarin/ios-samples/blob/master/UIImageEffects/UIImageEffects.cs
        /// </remarks>
        private static UIImage InternalCreateBlurredImage(UIImage image, float blurRadius)
        {
            var imageRect   = new CGRect(CGPoint.Empty, image.Size);
            var effectImage = image;

            UIGraphics.BeginImageContextWithOptions(image.Size, false, UIScreen.MainScreen.Scale);
            var contextIn = UIGraphics.GetCurrentContext();

            contextIn.ScaleCTM(1.0f, -1.0f);
            contextIn.TranslateCTM(0, -image.Size.Height);
            contextIn.DrawImage(imageRect, image.CGImage);
            var effectInContext = contextIn.AsBitmapContext() as CGBitmapContext;

            var effectInBuffer = new vImageBuffer()
            {
                Data        = effectInContext.Data,
                Width       = ( int )effectInContext.Width,
                Height      = ( int )effectInContext.Height,
                BytesPerRow = ( int )effectInContext.BytesPerRow
            };

            UIGraphics.BeginImageContextWithOptions(image.Size, false, UIScreen.MainScreen.Scale);
            var effectOutContext = UIGraphics.GetCurrentContext().AsBitmapContext() as CGBitmapContext;
            var effectOutBuffer  = new vImageBuffer()
            {
                Data        = effectOutContext.Data,
                Width       = ( int )effectOutContext.Width,
                Height      = ( int )effectOutContext.Height,
                BytesPerRow = ( int )effectOutContext.BytesPerRow
            };

            var  inputRadius = blurRadius * UIScreen.MainScreen.Scale / 2.0f;
            uint radius      = ( uint )(Math.Floor(inputRadius * 3 * Math.Sqrt(2 * Math.PI) / 4 + 0.5));

            if ((radius % 2) != 1)
            {
                radius += 1;
            }
            vImage.BoxConvolveARGB8888(ref effectInBuffer, ref effectOutBuffer, IntPtr.Zero, 0, 0, radius, radius, Pixel8888.Zero, vImageFlags.EdgeExtend);
            vImage.BoxConvolveARGB8888(ref effectOutBuffer, ref effectInBuffer, IntPtr.Zero, 0, 0, radius, radius, Pixel8888.Zero, vImageFlags.EdgeExtend);
            vImage.BoxConvolveARGB8888(ref effectInBuffer, ref effectOutBuffer, IntPtr.Zero, 0, 0, radius, radius, Pixel8888.Zero, vImageFlags.EdgeExtend);

            effectImage = UIGraphics.GetImageFromCurrentImageContext();
            UIGraphics.EndImageContext();
            UIGraphics.EndImageContext();

            // Setup up output context
            UIGraphics.BeginImageContextWithOptions(image.Size, false, UIScreen.MainScreen.Scale);
            var outputContext = UIGraphics.GetCurrentContext();

            outputContext.ScaleCTM(1, -1);
            outputContext.TranslateCTM(0, -image.Size.Height);

            // Draw base image
            outputContext.SaveState();
            outputContext.DrawImage(imageRect, effectImage.CGImage);
            outputContext.RestoreState();
            var outputImage = UIGraphics.GetImageFromCurrentImageContext();

            UIGraphics.EndImageContext();

            return(outputImage);
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Applies the blur with radius.
        /// </summary>
        /// <returns>The blur with radius.</returns>
        /// <param name="target">Target.</param>
        /// <param name="blurRadius">Blur radius.</param>
        /// <param name="tintColor">Tint color.</param>
        /// <param name="saturationDeltaFactor">Saturation delta factor.</param>
        /// <param name="maskImage">Mask image.</param>
        public static UIImage ApplyBlurWithRadius(this UIImage target, nfloat blurRadius, UIColor tintColor, nfloat saturationDeltaFactor, UIImage maskImage)
        {
            // Check pre-conditions.
            if (target.Size.Width < 1 || target.Size.Height < 1)
            {
                throw new Exception(String.Format(@"*** error: invalid size: (%.2 x %.2f). Both dimensions must be >= 1: %@", target.Size.Width, target.Size.Height, target));
            }
            if (target.CGImage == null)
            {
                throw new Exception(String.Format(@"*** error: image must be backed by a CGImage: %@", target));
            }

            if (maskImage != null &&
                maskImage.CGImage == null)
            {
                throw new Exception(String.Format(@"*** error: maskImage must be backed by a CGImage: %@", maskImage));
            }

            //
            var imageRect = new CGRect(CGPoint.Empty, target.Size);

            var effectImage = target;
            //
            bool hasBlur             = blurRadius > float.Epsilon;
            bool hasSaturationChange = Math.Abs(saturationDeltaFactor - 1d) > float.Epsilon;


            if (hasBlur || hasSaturationChange)
            {
                UIGraphics.BeginImageContextWithOptions(target.Size, false, UIScreen.MainScreen.Scale);

                var effectInContext = UIGraphics.GetCurrentContext();
                effectInContext.ScaleCTM(1.0f, -1.0f);

                effectInContext.TranslateCTM(0, -target.Size.Height);
                effectInContext.DrawImage(imageRect, target.CGImage);


                var gctx = effectInContext.AsBitmapContext();

                vImageBuffer effectInBuffer = new vImageBuffer();

                effectInBuffer.Data        = gctx.Data;
                effectInBuffer.Width       = (int)gctx.Width;
                effectInBuffer.Height      = (int)gctx.Height;
                effectInBuffer.BytesPerRow = (int)gctx.BytesPerRow;


                UIGraphics.BeginImageContextWithOptions(target.Size, false, UIScreen.MainScreen.Scale);

                var effectOutContext = UIGraphics.GetCurrentContext();
                var gctxOut          = effectOutContext.AsBitmapContext();

                vImageBuffer effectOutBuffer = new vImageBuffer();
                effectOutBuffer.Data        = gctxOut.Data;
                effectOutBuffer.Width       = (int)gctxOut.Width;
                effectOutBuffer.Height      = (int)gctxOut.Height;
                effectOutBuffer.BytesPerRow = (int)gctxOut.BytesPerRow;

                if (hasBlur)
                {
                    // A description of how to compute the box kernel width from the Gaussian
                    // radius (aka standard deviation) appears in the SVG spec:
                    // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
                    //
                    // For larger values of 's' (s >= 2.0), an approximation can be used: Three
                    // successive box-blurs build a piece-wise quadratic convolution kernel, which
                    // approximates the Gaussian kernel to within roughly 3%.
                    //
                    // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
                    //
                    // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
                    //
                    var inputRadius = blurRadius * UIScreen.MainScreen.Scale;


                    var radius = (uint)Math.Floor(inputRadius * 3f * Math.Sqrt(2 * Math.PI) / 4 + 0.5);

                    if (radius % 2 != 1)
                    {
                        radius += 1;          // force radius to be odd so that the three box-blur methodology works.
                    }


                    vImage.BoxConvolveARGB8888(ref effectInBuffer, ref effectOutBuffer, IntPtr.Zero, 0, 0, radius, radius, Pixel8888.Zero, vImageFlags.EdgeExtend);
                    vImage.BoxConvolveARGB8888(ref effectOutBuffer, ref effectInBuffer, IntPtr.Zero, 0, 0, radius, radius, Pixel8888.Zero, vImageFlags.EdgeExtend);
                    vImage.BoxConvolveARGB8888(ref effectInBuffer, ref effectOutBuffer, IntPtr.Zero, 0, 0, radius, radius, Pixel8888.Zero, vImageFlags.EdgeExtend);
                }

                bool effectImageBuffersAreSwapped = false;

                if (hasSaturationChange)
                {
                    var s = saturationDeltaFactor;
                    var floatingPointSaturationMatrix = new nfloat[] {
                        0.0722f + 0.9278f * s, 0.0722f - 0.0722f * s, 0.0722f - 0.0722f * s, 0f,
                        0.7152f - 0.7152f * s, 0.7152f + 0.2848f * s, 0.7152f - 0.7152f * s, 0f,
                        0.2126f - 0.2126f * s, 0.2126f - 0.2126f * s, 0.2126f + 0.7873f * s, 0f,
                        0f, 0f, 0f, 1f,
                    };

                    var divisor = 256;

                    var matrixSize = floatingPointSaturationMatrix.Length / sizeof(float);

                    var saturationMatrix = new short[matrixSize];

                    for (int i = 0; i < matrixSize; ++i)
                    {
                        saturationMatrix[i] = (short)Math.Round(floatingPointSaturationMatrix[i] * divisor);
                    }

                    if (hasBlur)
                    {
                        vImage.MatrixMultiplyARGB8888(ref effectOutBuffer, ref effectInBuffer, saturationMatrix, divisor, null, null, vImageFlags.NoFlags);
                        effectImageBuffersAreSwapped = true;
                    }
                    else
                    {
                        vImage.MatrixMultiplyARGB8888(ref effectInBuffer, ref effectOutBuffer, saturationMatrix, divisor, null, null, vImageFlags.NoFlags);
                    }
                }

                if (!effectImageBuffersAreSwapped)
                {
                    effectImage = UIGraphics.GetImageFromCurrentImageContext();
                }

                UIGraphics.EndImageContext();

                if (effectImageBuffersAreSwapped)
                {
                    effectImage = UIGraphics.GetImageFromCurrentImageContext();
                }

                UIGraphics.EndImageContext();
            }



            // Set up output context.
            UIGraphics.BeginImageContextWithOptions(target.Size, false, UIScreen.MainScreen.Scale);
            var outputContext = UIGraphics.GetCurrentContext();

            outputContext.ScaleCTM(1.0f, -1.0f);
            outputContext.TranslateCTM(0, -target.Size.Height);

            // Draw base image.
            outputContext.DrawImage(imageRect, target.CGImage);

            // Draw effect image.
            if (hasBlur)
            {
                outputContext.SaveState();

                if (maskImage != null)
                {
                    outputContext.ClipToMask(imageRect, maskImage.CGImage);
                }

                outputContext.DrawImage(imageRect, effectImage.CGImage);
                outputContext.RestoreState();
            }

            // Add in color tint.
            if (tintColor != null)
            {
                outputContext.SaveState();
                outputContext.SetFillColor(tintColor.CGColor);
                outputContext.FillRect(imageRect);
                outputContext.RestoreState();
            }

            UIImage outputImage = UIGraphics.GetImageFromCurrentImageContext();

            UIGraphics.EndImageContext();
            //
            return(outputImage);
        }
		unsafe public static vImageError Planar16FtoPlanarF (ref vImageBuffer src, ref vImageBuffer dest, vImageFlags flags)
		{
			return (vImageError)(long)vImageConvert_Planar16FtoPlanarF (ref src, ref dest, flags);
		}
		extern static nint vImageConvert_Planar16FtoPlanarF (ref vImageBuffer src, ref vImageBuffer dest, vImageFlags flags);
		/// <summary>
		/// This function reads the output probabilities from finalLayer to CPU, sorts them and gets the label with heighest probability
		/// </summary>
		/// <param name="finalLayer">output image of the network this has probabilities of each digit</param>
		/// <returns>Guess of the network as to what the digit is as uint</returns>
		public uint GetLabel (MPSImage finalLayer)
		{
			// even though we have 10 labels outputed the MTLTexture format used is RGBAFloat16 thus 3 slices will have 3*4 = 12 outputs
			var resultHalfArray = Enumerable.Repeat ((ushort)6, 12).ToArray ();
			var resultHalfArrayHandle = GCHandle.Alloc (resultHalfArray, GCHandleType.Pinned);
			var resultHalfArrayPtr = resultHalfArrayHandle.AddrOfPinnedObject ();

			var resultFloatArray = Enumerable.Repeat (0.3f, 10).ToArray ();
			var resultFloatArrayHandle = GCHandle.Alloc (resultFloatArray, GCHandleType.Pinned);
			var resultFloatArrayPtr = resultFloatArrayHandle.AddrOfPinnedObject ();

			for (uint i = 0; i <= 2; i++) {
				finalLayer.Texture.GetBytes (resultHalfArrayPtr + 4 * (int)i * sizeof (ushort),
											sizeof (ushort) * 1 * 4, sizeof (ushort) * 1 * 1 * 4,
											new MTLRegion (new MTLOrigin (0, 0, 0), new MTLSize (1, 1, 1)),
											0, i);
			}

			// we use vImage to convert our data to float16, Metal GPUs use float16 and swift float is 32-bit
			var fullResultVImagebuf = new vImageBuffer {
				Data = resultFloatArrayPtr,
				Height = 1,
				Width = 10,
				BytesPerRow = 10 * 4
			};

			var halfResultVImagebuf = new vImageBuffer {
				Data = resultHalfArrayPtr,
				Height = 1,
				Width = 10,
				BytesPerRow = 10 * 2
			};

			if (Planar16FtoPlanarF (ref halfResultVImagebuf, ref fullResultVImagebuf, 0) != vImageError.NoError)
				Console.WriteLine ("Error in vImage");

			// poll all labels for probability and choose the one with max probability to return
			float max = 0f;
			uint mostProbableDigit = 10;

			for (uint i = 0; i <= 9; i++) {
				if (max < resultFloatArray [i]) {
					max = resultFloatArray [i];
					mostProbableDigit = i;
				}
			}

			resultHalfArrayHandle.Free ();
			resultFloatArrayHandle.Free ();

			return mostProbableDigit;
		}