Gif frames overlapped, ghosting effect
I have a gif with transparent background. After processing it through cloudinary the output has a ghosting effect. I tried different parameters to prevent it i.e.: fl_keep_iptc, fl_keep_attribution, fl_lossy, q_100 but it still does not work.
Thanks for your help,
It's here: https://i.ibb.co/0mQvNtZ/anim-trans.gif
It's worth mentioning it's displayed in iOS application, so in a web browser you'll see no difference.
A few follow-up questions:
1. How are you displaying the image in an iOS app? In a UIImageView? (This is complicated: https://stackoverflow.com/questions/45745432/ios-11-animated-gif-display-in-uiimageview) In a UIWebView? Some other way?
2. I'm not clear whether or not the image at https://i.ibb.co/0mQvNtZ/anim-trans.gif has been processed by Cloudinary, or not. Can you give us two URLs:
- the original gif (does this have a ghosting effect?)
- a Cloudinary URL showing which tranformations, if any, have been applied, which generates a gif which exhibits the ghosting.
1. I need to embed a gif inside a video, so I use a technique similar to: http://www.mikitamanko.com/blog/2017/05/21/swift-how-to-insert-animated-watermark-into-the-video-or-how-to-merge-two-videos/ . What interests us the most is `getFramesAnimation` method. Passed frames parameter (images) is calculated via extracting gif data using Image I/O (CGImageSourceCreateImageAtIndex to create images and kCGImagePropertyGIFDictionary to get delays). What's interesting, when I display one random frame from an array of frames as static image, it has a ghosting effect too.
- original gif: https://i.ibb.co/sszWhY9/anim-trans.gif
it does not have a ghosting effect right now (it had a little ghosting effect before, but I processed it via GIFSicle - I used https://graphicdesign.stackexchange.com/a/44392 except for `transparent` and `02` options)
- cloudinary url: https://res.cloudinary.com/.../image/fetch/fl_keep_iptc,fl_keep_attribution/https://i.ibb.co/sszWhY9/anim-trans.gif (that's an example, parameters doesn't have any impact)
This url https://i.ibb.co/sszWhY9/anim-trans.gif and this https://res.cloudinary.com/.../image/fetch/fl_keep_iptc,fl_keep_attribution/https://i.ibb.co/sszWhY9/anim-trans.gif produce exactly the same file as far as I can see.
Is the complaint then that Cloudinary does not produce a GIF that is correctly rendered in your iOS code? Are you seeing different "ghosting" effects when using the original URL vs. the one from Cloudinary?
The GIF format supports several disposal methods (as you might know). Here is an excerpt from https://www.w3.org/Graphics/GIF/spec-gif89a.txt:
iv) Disposal Method - Indicates the way in which the graphic is to be treated after being displayed. Values : 0 - No disposal specified. The decoder is not required to take any action. 1 - Do not dispose. The graphic is to be left in place. 2 - Restore to background color. The area used by the graphic must be restored to the background color. 3 - Restore to previous. The decoder is required to restore the area overwritten by the graphic with what was there prior to rendering the graphic. 4-7 - To be defined.
I'm guessing you would want Cloudinary to respond with an "unoptimized" GIF (0)? Same as you produced with GIFSicle?
By default we are targeting browsers and are making sure files are as small as possible without losing quality. As you said, this file displays fine in a browser so we will need to evaluate this particular use-case.
Note that disposal happens on a per frame basis.
Both of the files that you sent have every frame set to disposal value "3" here – restore to previous. With these settings, every frame is supposed to be painted on a blank canvas.
I am not sure what GifSicle is doing, but I think that Cloudinary's behavior here – preserving the disposal settings from the original gif passed to it – is correct, and the iOS code is definitely not respecting the disposal values present in the GIF.
I am not familiar enough with Core Animation to say so definitively, but I strongly suspect that the `getFramesAnimation` method here is assuming a value of "1" for all frames. I do not currently have an environment set up where I can try to replicate the bug or fiddle with the method, but the first thing that I would try, based on this documentation:
would be to change this line of the method:
animation.fillMode = kCAFillModeForwards
animation.fillMode = kCAFillModeRemoved
Let us know if that fixes the issue. If it doesn’t, I can only recommend finding a different library to handle animated GIFs, which respects frames' disposal settings.
(Alternatively, you might be able to achieve the overlay with Cloudinary! See the "Animated GIFs Overlayed Onto Videos" section here: https://cloudinary.com/cookbook/adding_video_watermark_as_an_overlay)
Thanks for your replies. I introduced some inconsistencies, because I firstly thought that parameters passed to cloudinary does not have any impact on output file regarding to ghosting effect, but I was wrong.
That's a file without a ghosting effect: https://res.cloudinary.com/.../image/fetch/https://i.ibb.co/sszWhY9/anim-trans.gif
The output looks like that: https://streamable.com/vs6ql
And that's a resized file with a ghosting effect: https://res.cloudinary.com/.../image/fetch/h_360,w_360/https://i.ibb.co/sszWhY9/anim-trans.gif
The output is: https://streamable.com/trc3a
I checked the metadata of the gifs above. The original file has `restoreToPrevious` disposal method and the file after resizing has it set as `doNotDispose`. It looks like you change the disposal method when resizing.
I added two additional parameters to retain metadata: https://res.cloudinary.com/.../image/fetch/h_360,w_360,fl_keep_iptc,fl_keep_attribution/https://i.ibb.co/sszWhY9/anim-trans.gif but it's still converting to `doNotDispose` disposal method.
Am I right that `fl_keep_iptc` and `fl_keep_attribution` attributes do not work correctly in this particular case?
BTW Only one iOS library can display gifs with `doNotDispose` option (it's called FLAnimatedImage), but the mechanism they're using is strictly connected with UIImageView and CADisplaylink and it's useless in my case (I'm using layer's contents to embed a gif in a video).
Changing animation's fillMode to kCAFillModeRemoved didn't help. Displaying a single frame without animation has a ghosting effect too.
Thanks for your patience and sorry for the delayed response.
Also, sorry for the incorrect information, earlier. You're right that we're changing the disposal modes! Some initial research suggests an answer to why the changes we're making are invisible in browsers but visible in iOS... but let me do some more research and get back to you with a definitive answer, and what we intend to do about it on our side, going forward.
The keep_iptc and keep_attribution flags deal with EXIF/IPTC metadata -- not the gif-specific metadata that these disposal settings live within. These flags don't (and shouldn't) affect disposal modes.
So, one of the ways that we're optimizing the GIF (which we do whenever we transform it in any way) is by changing it so that only the parts of the gif that have changed are encoded in each frame. This requires changing the disposal mode from "Restore to previous" (which your original gif used to paint each frame entirely from scratch) to "Restore to background". Most GIF decoders, and all web browsers, "restore" to transparency. Apparently, Apple's libraries restore to the background color of the GIF – in this case, white. Thus, the ghosting.
I don't think we can justify turning this optimization off by default. The bytesize gains are too large.
If you can't use Cloudinary to do the overlay, would it be possible to convert the GIF to a different format that is properly decoded by Apple's libraries – perhaps APNG?
Thanks for investigation of the issue. I followed your suggestion with using APNG and now it works - the ghosting effect disappeared. The drawback of this solution is time-consuming conversion. For full size gifs it takes about 60~ seconds.
BTW Do you know if it is possible that the gif itself is malformed (regardless of a disposal method)? I use about hundred of gifs and a ghosting effect happens to only two of them.
From what I've noticed:
- Disposal method of these two gifs: https://res.cloudinary.com/.../image/fetch/https://i.ibb.co/Qfdy1pH/zum-one.gif and https://res.cloudinary.com/.../image/fetch/https://i.ibb.co/74tkVxM/zum-big-one.gif is changed (or remains the same) after resizing via Cloudinary to `doNotDispose` and it causes ghosting effect.
When I change disposal method to `restoreToPrevious` or `restoreToBackgroundColor` before resizing, it still converts it to `doNotDispose`.
- Disposal method of gifs (just two examples): https://res.cloudinary.com/.../image/fetch/https://i.ibb.co/XLk94yZ/zum-three.gif and https://res.cloudinary.com/.../image/fetch/https://i.ibb.co/pzLS3GP/zum-three-men-woman.gif is changed (or remains the same) after resizing via Cloudinary to `restoreToBackgroundColor` and it doesn't cause ghosting effect.
So, the problem is that for two of my gifs (zum-one and zum-big-one) disposal method is always changed to `doNotDispose` after resizing and for the others it's `restoreToBackgroundColor` which is ok.
Post is closed for comments.