protected virtual async Task<WithLoadingResult<UIImage>> GetImageAsync(string sourcePath, ImageSource source, 
			bool isPlaceholder, Stream originalStream = null)
		{
			if (CancellationToken.IsCancellationRequested)
				return null;

			UIImage image = null;
			byte[] bytes = null;
			string path = sourcePath;
			LoadingResult? result = null;

			try
			{
				if (originalStream != null)
				{
					try
					{
						using (var ms = new MemoryStream())
						{
							await originalStream.CopyToAsync(ms).ConfigureAwait(false);
							bytes = ms.ToArray();
							path = sourcePath;
							result = LoadingResult.Stream;
						}
					}
					finally
					{
						originalStream.Dispose();
					}
				}
				else
				{
					using (var resolver = DataResolverFactory.GetResolver(source, Parameters, DownloadCache, MainThreadDispatcher))
					{
						var data = await resolver.GetData(path, CancellationToken.Token).ConfigureAwait(false);
						if (data == null)
							return null;

						image = data.Image;
						bytes = data.Data;
						path = data.ResultIdentifier;
						result = data.Result;
					}
				}
			}
			catch (System.OperationCanceledException)
			{
				Logger.Debug(string.Format("Image request for {0} got cancelled.", path));
				return null;
			}
			catch (Exception ex)
			{
				var message = String.Format("Unable to retrieve image data from source: {0}", sourcePath);
				Logger.Error(message, ex);
				Parameters.OnError(ex);
				return null;
			}

			if (bytes == null && image == null)
				return null;

			var imageToDisplay = await Task.Run(() =>
				{
					if (CancellationToken.IsCancellationRequested)
						return null;

					UIImage imageIn = image;

					if (imageIn == null)
					{
						// Special case to handle WebP decoding on iOS
						if (sourcePath.ToLowerInvariant().EndsWith(".webp", StringComparison.InvariantCulture))
						{
							imageIn = new WebP.Touch.WebPCodec().Decode(bytes);
						}
						else
						{
							nfloat scale = _imageScale >= 1 ? _imageScale : _screenScale;
							imageIn = new UIImage(NSData.FromArray(bytes), scale);
						}
					}

					if (Parameters.DownSampleSize != null
						&& ((Parameters.DownSampleSize.Item1 > 0 && imageIn.Size.Width > Parameters.DownSampleSize.Item1) 
							|| (Parameters.DownSampleSize.Item2 > 0 && imageIn.Size.Height > Parameters.DownSampleSize.Item2)))
					{
						var tempImage = imageIn;

						int downsampleWidth = Parameters.DownSampleSize.Item1;
						int downsampleHeight = Parameters.DownSampleSize.Item2;

						if (Parameters.DownSampleUseDipUnits)
						{
							downsampleWidth = downsampleWidth.PointsToPixels();
							downsampleHeight = downsampleHeight.PointsToPixels();
						}

						imageIn = tempImage.ResizeUIImage(downsampleWidth, downsampleHeight, Parameters.DownSampleInterpolationMode);
						tempImage.Dispose();
					}

					bool transformPlaceholdersEnabled = Parameters.TransformPlaceholdersEnabled.HasValue ? 
						Parameters.TransformPlaceholdersEnabled.Value : ImageService.Config.TransformPlaceholders;

					if (Parameters.Transformations != null && Parameters.Transformations.Count > 0 
						&& (!isPlaceholder || (isPlaceholder && transformPlaceholdersEnabled)))
					{
						foreach (var transformation in Parameters.Transformations.ToList() /* to prevent concurrency issues */)
						{
							if (CancellationToken.IsCancellationRequested)
								return null;

							try
							{
								var old = imageIn;
								var bitmapHolder = transformation.Transform(new BitmapHolder(imageIn));
								imageIn = bitmapHolder.ToNative();

								// Transformation succeeded, so garbage the source
								if (old != null && old != imageIn && old.Handle != imageIn.Handle)
									old.Dispose();
							}
							catch (Exception ex)
							{
								Logger.Error("Can't apply transformation " + transformation.Key + " to image " + path, ex);
							}
						}
					}

					return imageIn;
				}).ConfigureAwait(false);

			bytes = null;

			return WithLoadingResult.Encapsulate(imageToDisplay, result.Value);
		}
		protected virtual async Task<WithLoadingResult<UIImage>> GetImageAsync(string sourcePath, ImageSource source, 
			bool isPlaceholder, Stream originalStream = null)
		{
			if (IsCancelled)
				return new WithLoadingResult<UIImage>(LoadingResult.Canceled);

			LoadingResult? result = null;
			UIImage image = null;
			byte[] bytes = null;
			string path = sourcePath;
			ImageInformation imageInformation = null;

			try
			{
				if (originalStream != null)
				{
					try
					{
						// check is stream is memorystream
						var ms = originalStream as MemoryStream;
						if (ms != null)
						{
							bytes = ms.ToArray();
						}
						else if (originalStream.CanSeek)
						{
							bytes = new byte[originalStream.Length];
							await originalStream.ReadAsync(bytes, 0, (int)originalStream.Length, CancellationToken.Token).ConfigureAwait(false);
						}
						else
						{
							using (var ms2 = new MemoryStream())
							{
								await originalStream.CopyToAsync(ms2).ConfigureAwait(false);
								bytes = ms2.ToArray();
							}
						}

						path = sourcePath;
						result = LoadingResult.Stream;
					}
					finally
					{
						originalStream.Dispose();
					}
				}
				else
				{
					using (var resolver = DataResolverFactory.GetResolver(source, Parameters, DownloadCache, MainThreadDispatcher))
					{
						var data = await resolver.GetData(path, CancellationToken.Token).ConfigureAwait(false);
						if (data == null)
							return new WithLoadingResult<UIImage>(LoadingResult.Failed);

						image = data.Image;
						bytes = data.Data;
						path = data.ResultIdentifier;
						result = data.Result;
						imageInformation = data.ImageInformation;
					}
				}
			}
			catch (OperationCanceledException)
			{
				Logger.Debug(string.Format("Image request for {0} got cancelled.", path));
				return new WithLoadingResult<UIImage>(LoadingResult.Canceled);
			}
			catch (Exception ex)
			{
				var message = String.Format("Unable to retrieve image data from source: {0}", sourcePath);
				Logger.Error(message, ex);
				Parameters.OnError(ex);
				return new WithLoadingResult<UIImage>(LoadingResult.Failed);
			}

			if (bytes == null && image == null)
			{
				if (result != null && (int)result<0) // it's below zero if it's an error
					return new WithLoadingResult<UIImage>(result.Value);
				else
					return new WithLoadingResult<UIImage>(LoadingResult.Failed);
			}

			if (IsCancelled)
				return new WithLoadingResult<UIImage>(LoadingResult.Canceled);

			UIImage imageIn = image;
			NSData nsdata = null;

			if (imageIn == null)
			{
				// Special case to handle WebP decoding on iOS
				if (sourcePath.ToLowerInvariant().EndsWith(".webp", StringComparison.InvariantCulture))
				{
					imageIn = new WebP.Touch.WebPCodec().Decode(bytes);
				}
				else
				{
					// nfloat scale = _imageScale >= 1 ? _imageScale : ScaleHelper.Scale;
					nsdata = NSData.FromArray(bytes);
					if (nsdata == null)
						return new WithLoadingResult<UIImage>(LoadingResult.Failed);
				}
			}

			bytes = null;

			// Setting image informations
			if (imageInformation == null)
				imageInformation = new ImageInformation();
			
			imageInformation.SetKey(path == "Stream" ? GetKey() : GetKey(sourcePath), Parameters.CustomCacheKey);

			// We rely on ImageIO for all datasources except AssetCatalog, this way we don't generate temporary UIImage
			// furthermore we can do all the work in a thread safe way and in threadpool
			if (nsdata != null)
			{
				int downsampleWidth = Parameters.DownSampleSize?.Item1 ?? 0;
				int downsampleHeight = Parameters.DownSampleSize?.Item2 ?? 0;

				if (Parameters.DownSampleUseDipUnits)
				{
					downsampleWidth = downsampleWidth.PointsToPixels();
					downsampleHeight = downsampleHeight.PointsToPixels();
				}

				imageIn = nsdata.ToImage(new CoreGraphics.CGSize(downsampleWidth, downsampleHeight), ScaleHelper.Scale, NSDataExtensions.RCTResizeMode.ScaleAspectFill, imageInformation);
			}
			else if (Parameters.DownSampleSize != null && imageIn != null)
			{
				// if we already have the UIImage in memory it doesn't really matter to resize it
				// furthermore this will only happen for AssetCatalog images (yet)
			}
			
			bool transformPlaceholdersEnabled = Parameters.TransformPlaceholdersEnabled.HasValue ? 
				Parameters.TransformPlaceholdersEnabled.Value : ImageService.Instance.Config.TransformPlaceholders;

			if (Parameters.Transformations != null && Parameters.Transformations.Count > 0 
				&& (!isPlaceholder || (isPlaceholder && transformPlaceholdersEnabled)))
			{
				foreach (var transformation in Parameters.Transformations.ToList() /* to prevent concurrency issues */)
				{
					if (IsCancelled)
						return new WithLoadingResult<UIImage>(LoadingResult.Canceled);

					try
					{
						var old = imageIn;
						var bitmapHolder = transformation.Transform(new BitmapHolder(imageIn));
						imageIn = bitmapHolder.ToNative();

						// Transformation succeeded, so garbage the source
						if (old != null && old != imageIn && old.Handle != imageIn.Handle)
							old.Dispose();
					}
					catch (Exception ex)
					{
						Logger.Error("Can't apply transformation " + transformation.Key + " to image " + path, ex);
					}
				}
			}
				
			return WithLoadingResult.Encapsulate(imageIn, result.Value, imageInformation);
		}