diff --git a/samples/ImageLoading.Forms.Sample/Shared/FFImageLoading.Forms.Sample.csproj b/samples/ImageLoading.Forms.Sample/Shared/FFImageLoading.Forms.Sample.csproj index 5b0a59149..e7ae43fd3 100644 --- a/samples/ImageLoading.Forms.Sample/Shared/FFImageLoading.Forms.Sample.csproj +++ b/samples/ImageLoading.Forms.Sample/Shared/FFImageLoading.Forms.Sample.csproj @@ -147,6 +147,10 @@ StreamListPageCell.xaml + + + SimpleGifPage.xaml + @@ -278,6 +282,9 @@ MSBuild:UpdateDesignTimeXaml + + MSBuild:UpdateDesignTimeXaml + diff --git a/samples/ImageLoading.Forms.Sample/Shared/Pages/MenuPageModel.cs b/samples/ImageLoading.Forms.Sample/Shared/Pages/MenuPageModel.cs index 1c511f1df..eaa6236cf 100644 --- a/samples/ImageLoading.Forms.Sample/Shared/Pages/MenuPageModel.cs +++ b/samples/ImageLoading.Forms.Sample/Shared/Pages/MenuPageModel.cs @@ -30,6 +30,15 @@ public MenuPageModel() }) }, + new MenuItem() { + Section = "Basic", + Title = "Simple Gif", + Command = new BaseCommand(async (param) => + { + await this.PushPageFromCacheAsync(pm => pm.Reload()); + }) + }, + new MenuItem() { Section = "Basic", Title = "Placeholders examples", diff --git a/samples/ImageLoading.Forms.Sample/Shared/Pages/SimpleGifPage.xaml b/samples/ImageLoading.Forms.Sample/Shared/Pages/SimpleGifPage.xaml new file mode 100644 index 000000000..887fe3ce1 --- /dev/null +++ b/samples/ImageLoading.Forms.Sample/Shared/Pages/SimpleGifPage.xaml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/samples/ImageLoading.Forms.Sample/Shared/Pages/SimpleGifPage.xaml.cs b/samples/ImageLoading.Forms.Sample/Shared/Pages/SimpleGifPage.xaml.cs new file mode 100644 index 000000000..f33abd8ea --- /dev/null +++ b/samples/ImageLoading.Forms.Sample/Shared/Pages/SimpleGifPage.xaml.cs @@ -0,0 +1,13 @@ +using Xamarin.Forms; +using Xamvvm; + +namespace FFImageLoading.Forms.Sample +{ + public partial class SimpleGifPage : ContentPage, IBasePage + { + public SimpleGifPage() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/samples/ImageLoading.Forms.Sample/Shared/Pages/SimpleGifPageModel.cs b/samples/ImageLoading.Forms.Sample/Shared/Pages/SimpleGifPageModel.cs new file mode 100644 index 000000000..c8254c0ac --- /dev/null +++ b/samples/ImageLoading.Forms.Sample/Shared/Pages/SimpleGifPageModel.cs @@ -0,0 +1,15 @@ +using Xamvvm; + +namespace FFImageLoading.Forms.Sample +{ + [PropertyChanged.ImplementPropertyChanged] + public class SimpleGifPageModel : BasePageModel + { + public void Reload() + { + ImageUrl = "https://media.giphy.com/media/l0Hlyi4ZMJI9MpFUQ/giphy.gif"; + } + + public string ImageUrl { get; set; } + } +} diff --git a/source/FFImageLoading.Touch/FFImageLoading.Touch.csproj b/source/FFImageLoading.Touch/FFImageLoading.Touch.csproj index 5914fb13a..43768e546 100644 --- a/source/FFImageLoading.Touch/FFImageLoading.Touch.csproj +++ b/source/FFImageLoading.Touch/FFImageLoading.Touch.csproj @@ -89,6 +89,7 @@ + diff --git a/source/FFImageLoading.Touch/Helpers/GifHelper.cs b/source/FFImageLoading.Touch/Helpers/GifHelper.cs new file mode 100644 index 000000000..cbe2b072a --- /dev/null +++ b/source/FFImageLoading.Touch/Helpers/GifHelper.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.Linq; +using Foundation; +using ImageIO; +using UIKit; + +namespace FFImageLoading.Helpers +{ + public static class GifHelper + { + public static UIImage AnimateGif(NSData data) + { + if (data?.Length == 0) + return null; + + using (var source = CGImageSource.FromData(data)) + return AnimateGifFromSource(source) ?? UIImage.LoadFromData(data); + } + + private static UIImage AnimateGifFromSource(CGImageSource source) + { + var frameCount = source?.ImageCount; + + // no need to animate + if (frameCount <= 1) + return null; + + var frames = GetFrames(source); + var delays = GetDelays(source); + var totalDuration = delays.Sum(); + + // SUPER BASIC. Does not respect variable length frames. No memory optimizations. + return UIImage.CreateAnimatedImage(frames.ToArray(), totalDuration); + } + + private static List GetFrames(CGImageSource source) + { + var retval = new List(); + + for (int i = 0; i < source?.ImageCount; i++) + { + using (var frameImage = source.CreateImage(i, null)) + retval.Add(UIImage.FromImage(frameImage)); + } + + return retval; + } + + private static List GetDelays(CGImageSource source) + { + var retval = new List(); + + for (int i = 0; i < source?.ImageCount; i++) + { + var properties = source.GetProperties(i, null); + using (var gifProperties = properties.Dictionary["{GIF}"]) + { + using (var delayTime = gifProperties.ValueForKey(new NSString("DelayTime"))) + { + var realDuration = double.Parse(delayTime.ToString()); + retval.Add(realDuration); + } + } + } + + return retval; + } + + private static int GetLoopCount(CGImageSource source) + { + var var = source.GetProperties(null); + using (var gifProperties = var.Dictionary["{GIF}"]) + { + var loopCount = gifProperties.ValueForKey(new NSString("LoopCount")); + return int.Parse(loopCount.ToString()); + } + } + } +} \ No newline at end of file diff --git a/source/FFImageLoading.Touch/Work/PlatformImageLoaderTask.cs b/source/FFImageLoading.Touch/Work/PlatformImageLoaderTask.cs index ed62ca9ba..280d79ab5 100644 --- a/source/FFImageLoading.Touch/Work/PlatformImageLoaderTask.cs +++ b/source/FFImageLoading.Touch/Work/PlatformImageLoaderTask.cs @@ -48,6 +48,12 @@ protected async override Task GenerateImageAsync(string path, ImageSour { imageIn = new WebP.Touch.WebPCodec().Decode(imageData); } + // Special case to handle gif animations on iOS + else if (path.ToLowerInvariant().EndsWith(".gif", StringComparison.InvariantCulture)) + { + using (var nsdata = NSData.FromStream(imageData)) + imageIn = GifHelper.AnimateGif(nsdata); + } else { var nsdata = NSData.FromStream(imageData);