<div dir="ltr"><div>Hi Christoph,</div><div><br></div><div>I am responsible for the version of GIFReadWriter that was altered in Pharo 7 and up. A couple of years ago I also found that many (most?) of the animated GIFs I was coming across in the wild were not displaying correctly inside of Squeak or Pharo. No one was working on the issue so I had to learn everything as I went. My own strategy was to "pull out" the LZW <a href="https://github.com/pharo-project/pharo/blob/Pharo9.0/src/Graphics-Files/LzwGifEncoder.class.st">compression</a> /<a href="https://github.com/pharo-project/pharo/blob/Pharo9.0/src/Graphics-Files/LzwGifDecoder.class.st"> decompression</a> components into their own classes. That helped me wrap my head around what's going on, since the original code is fairly complicated. It was not easy!<br></div><div>  <br></div><div>Additionally, the frames of the GIF images were not being composited correctly in the original implementation in either Squeak or Pharo. I took a very amateurish approach to that problem, I think, by creating a new class called AnimatedImageFrame that contains information about compositing disposal types, frame delay, the underlying Form, etc. I then implemented my own AnimatedImageMorph which is quite different from the Squeak version, designed to perform the correct compositions. I sort of regret this decision now, and feel like the compositing should happen with the GIF is decoded and that full  Forms should be made and passed to any AnimatedImageMorph. The disadvantage with that tactic, however, is that you lose the clever framing that went into the creation of the GIF -- large animated GIFs, when read in and written back, might balloon in size, since we would be saving all bits for each frame now rather than the original differences between frames.</div><div><br></div><div>The need for composited frames also led me to ditch the AnimatedGIFReadWriter entirely, and to fold in all functionality to <a href="https://github.com/pharo-project/pharo/blob/Pharo9.0/src/Graphics-Files/GIFReadWriter.class.st">GIFReadWriter</a>. The GIF spec does not make any distinction between a still or an animated image, other than the presence of multiple frames. This of course does not play nice with the ImageReadWriter pattern of #formFromStream: etc, so I made those older methods simply return a Form of the first frame. There are other methods for decoding the animated versions. There is, even in my implementation, no way for the system to determine if a GIF should be presented as an animated one or a still image of just the first or only frame.<br></div><div>  <br></div><div>I tried several times to port some of my changes partially to Squeak but kept running into roadblocks. The last one had to do with differences in how Squeak and Pharo deal with color bits and transparency. You can find that thread <a href="http://forum.world.st/GIFs-and-the-Color-black-td5120439.html">here</a>.</div><div><br></div><div>Also check out the class comments in the Pharo version of GIFReadWriter and the two LZW classes, since I think there are links to good information about the GIF format and some tutorials, too.</div><div><br></div><div>I can't remember the reasoning for every choice I made here but definitely hit me with any questions if you have them.<br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Fri, Oct 9, 2020 at 11:00 AM Thiede, Christoph <<a href="mailto:Christoph.Thiede@student.hpi.uni-potsdam.de">Christoph.Thiede@student.hpi.uni-potsdam.de</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">



<div>

<div id="gmail-m_2474855331146603683divtagdefaultwrapper" style="font-size:12pt;color:rgb(0,0,0);font-family:Calibri,Helvetica,sans-serif" dir="ltr">
<p>> <span style="font-size:12pt">And maybe double-check with the specification: <a href="https://www.w3.org/Graphics/GIF/spec-gif89a.txt" target="_blank">https://www.w3.org/Graphics/GIF/spec-gif89a.txt</a></span></p>
<div><br>
</div>
<p></p>
<div id="gmail-m_2474855331146603683Signature">
<div id="gmail-m_2474855331146603683divtagdefaultwrapper" dir="ltr" style="font-size:12pt;color:rgb(0,0,0);font-family:Calibri,Helvetica,sans-serif,EmojiFont,"Apple Color Emoji","Segoe UI Emoji",NotoColorEmoji,"Segoe UI Symbol","Android Emoji",EmojiSymbols">
<div name="divtagdefaultwrapper">
<div>
<div id="gmail-m_2474855331146603683Item.MessagePartBody">
<div id="gmail-m_2474855331146603683Item.MessageUniqueBody" style="font-family:wf_segoe-ui_normal,"Segoe UI","Segoe WP",Tahoma,Arial,sans-serif,serif,EmojiFont">
<div dir="ltr">
<div id="gmail-m_2474855331146603683divtagdefaultwrapper"><font face="Calibri,Helvetica,sans-serif,EmojiFont,Apple Color Emoji,Segoe UI Emoji,NotoColorEmoji,Segoe UI Symbol,Android Emoji,EmojiSymbols">
<div id="gmail-m_2474855331146603683Signature">
<div style="margin:0px"><font style="font-family:Calibri,Arial,Helvetica,sans-serif,serif,EmojiFont"></font></div>
</div>
</font></div>
</div>
</div>
</div>
<div id="gmail-m_2474855331146603683Item.MessagePartBody">This looks less funny to me, but you are right of course. :-)</div>
<div id="gmail-m_2474855331146603683Item.MessagePartBody"><br>
</div>
<div id="gmail-m_2474855331146603683Item.MessagePartBody">Best,</div>
<div id="gmail-m_2474855331146603683Item.MessagePartBody">Christoph</div>
</div>
<div><font size="2" color="#808080"></font></div>
</div>
</div>
</div>
</div>
<hr style="display:inline-block;width:98%">
<div id="gmail-m_2474855331146603683divRplyFwdMsg" dir="ltr"><font style="font-size:11pt" face="Calibri, sans-serif" color="#000000"><b>Von:</b> Squeak-dev <<a href="mailto:squeak-dev-bounces@lists.squeakfoundation.org" target="_blank">squeak-dev-bounces@lists.squeakfoundation.org</a>> im Auftrag von Taeumel, Marcel<br>
<b>Gesendet:</b> Freitag, 9. Oktober 2020 16:53:44<br>
<b>An:</b> squeak-dev<br>
<b>Betreff:</b> Re: [squeak-dev] The Inbox: Graphics-ct.443.mcz</font>
<div> </div>
</div>
<div>
<div id="gmail-m_2474855331146603683__MailbirdStyleContent" style="font-size:10pt;font-family:Arial;color:rgb(0,0,0)">
Hi Christoph.
<div><br>
</div>
<div>> <span style="font-family:Arial,Helvetica,sans-serif;font-size:13px">For a potentially relevant reference implementtion ...</span></div>
<div><span style="font-family:Arial,Helvetica,sans-serif;font-size:13px">And maybe double-check with the specification: </span><span style="font-size:10pt"><a href="https://www.w3.org/Graphics/GIF/spec-gif89a.txt" style="font-size:10pt" target="_blank">https://www.w3.org/Graphics/GIF/spec-gif89a.txt</a></span></div>
<div><br>
</div>
<div>Best,</div>
<div>Marcel</div>
<div></div>
<blockquote type="cite" style="border-left-style:solid;border-width:1px;margin-top:20px;margin-left:0px;padding-left:10px">
<p style="color:rgb(170,170,170);margin-top:10px">Am 09.10.2020 16:15:36 schrieb <a href="mailto:commits@source.squeak.org" target="_blank">commits@source.squeak.org</a> <<a href="mailto:commits@source.squeak.org" target="_blank">commits@source.squeak.org</a>>:</p>
<div style="font-family:Arial,Helvetica,sans-serif">Christoph Thiede uploaded a new version of Graphics to project The Inbox:<br>
<a href="http://source.squeak.org/inbox/Graphics-ct.443.mcz" target="_blank">http://source.squeak.org/inbox/Graphics-ct.443.mcz</a><br>
<br>
==================== Summary ====================<br>
<br>
Name: Graphics-ct.443<br>
Author: ct<br>
Time: 9 October 2020, 4:15:09.97236 pm<br>
UUID: 897346b7-ceaa-1a43-874f-3571d893309c<br>
Ancestors: Graphics-pre.439<br>
<br>
Adds basic support for storing an animated GIF file via AnimatedGIFReadWriter.<br>
<br>
Note that this implementation indeed is very rudimentary only. I found several GIF files on my computer that cannot be saved as a GIF correctly (something is wrong with the encoding of the background/alpha color). But on the other hand, I also met a number
 of GIF files that cannot be read by the AnimatedGIFReadWriter ("error: improper store") ...<br>
For a potentially relevant reference implementtion, I found this one, but I did not yet find the time apply it: <a href="https://gist.github.com/JimBobSquarePants/cac72c4e7d9f05f13ac9" target="_blank">https://gist.github.com/JimBobSquarePants/cac72c4e7d9f05f13ac9</a><br>
<br>
Following the idea of "baby steps", I'd like to get this into the Trunk at this early state however. The motivation behind this is that I would like to use this concept from another project so establishing the protocol as soon as possible would be helpful for
 me to write tests.<br>
<br>
=============== Diff against Graphics-pre.439 ===============<br>
<br>
Item was added:<br>
+ ----- Method: AnimatedGIFReadWriter class>>exampleAnim (in category 'examples') -----<br>
+ exampleAnim<br>
+ "AnimatedGIFReadWriter exampleAnim"<br>
+ <br>
+ | extent center frames |<br>
+ extent := 42 @ 42.<br>
+ center := extent // 2.<br>
+ frames := (2 to: center x - 1 by: 2) collect: [:r |<br>
+ "Make a fancy anim without using Canvas - inefficient as hell"<br>
+ | image |<br>
+ image := ColorForm extent: extent depth: 8.<br>
+ 0.0 to: 359.0 do: [:theta |<br>
+ image<br>
+ colorAt: (center + (Point r: r degrees: theta)) rounded<br>
+ put: Color red].<br>
+ image].<br>
+ <br>
+ ^ FileStream newFileNamed: 'anim.gif' do: [:stream |<br>
+ self<br>
+ putForms: frames<br>
+ andDelays: (frames withIndexCollect: [:frame :index |<br>
+ 10 + (index / frames size * 100)]) "Start fast, end slow"<br>
+ onStream: stream]!<br>
<br>
Item was added:<br>
+ ----- Method: AnimatedGIFReadWriter class>>putForms:andDelays:onStream: (in category 'image reading/writing') -----<br>
+ putForms: forms andDelays: delays onStream: aWriteStream<br>
+ "Store the given form sequence as an animation on the given stream."<br>
+ <br>
+ | writer canvas |<br>
+ self assert: forms size = delays size.<br>
+ <br>
+ writer := self on: aWriteStream.<br>
+ canvas := Form extent: forms first extent depth: 32.<br>
+ [Cursor write showWhile: [<br>
+ writer loopCount: -1.<br>
+ forms with: delays do: [:form :delay |<br>
+ writer delay: delay; flag: #todo. "ct: Does not work"<br>
+ form displayOn: canvas at: 0 @ 0 rule: Form over.<br>
+ writer nextPutImage: canvas]]]<br>
+ ensure: [writer close].!<br>
<br>
Item was added:<br>
+ ----- Method: AnimatedGIFReadWriter class>>putForms:onFileNamed: (in category 'image reading/writing') -----<br>
+ putForms: formSequence onFileNamed: fileName<br>
+ "Store the given form sequence as an animation on a file of the given name."<br>
+ <br>
+ FileStream newFileNamed: fileName do: [:stream |<br>
+ self putForms: formSequence onStream: stream].!<br>
<br>
Item was added:<br>
+ ----- Method: AnimatedGIFReadWriter class>>putForms:onStream: (in category 'image reading/writing') -----<br>
+ putForms: forms onStream: aWriteStream<br>
+ "Store the given form sequence as an animation on the given stream."<br>
+ <br>
+ ^ self<br>
+ putForms: forms<br>
+ andDelays: ((1 to: forms size) collect: [:i | 20])<br>
+ onStream: aWriteStream!<br>
<br>
Item was changed:<br>
----- Method: BMPReadWriter class>>readAllFrom: (in category 'testing') -----<br>
readAllFrom: fd<br>
"MessageTally spyOn:[BMPReadWriter readAllFrom: FileDirectory default]"<br>
fd fileNames do:[:fName|<br>
(fName endsWith: '.bmp') ifTrue:[<br>
+ [Form fromBinaryStream: (fd readOnlyFileNamed: fName)] ifError: [].<br>
- [Form fromBinaryStream: (fd readOnlyFileNamed: fName)] on: Error do:[:nix].<br>
].<br>
].<br>
fd directoryNames do:[:fdName|<br>
self readAllFrom: (fd directoryNamed: fdName)<br>
].!<br>
<br>
Item was removed:<br>
- ----- Method: GIFReadWriter class>>exampleAnim (in category 'examples') -----<br>
- exampleAnim<br>
- "GIFReadWriter exampleAnim"<br>
- <br>
- | writer extent center |<br>
- writer := GIFReadWriter on: (FileStream newFileNamed: 'anim.gif').<br>
- writer loopCount: 20. "Repeat 20 times"<br>
- writer delay: 10. "Wait 10/100 seconds"<br>
- extent := 42@42.<br>
- center := extent / 2.<br>
- Cursor write showWhile: [<br>
- [2 to: center x - 1 by: 2 do: [:r |<br>
- "Make a fancy anim without using Canvas - inefficient as hell"<br>
- | image |<br>
- image := ColorForm extent: extent depth: 8.<br>
- 0.0 to: 359.0 do: [:theta | image colorAt: (center + (Point r: r degrees: theta)) rounded put: Color red].<br>
- writer nextPutImage: image]<br>
- ] ensure: [writer close]].!<br>
<br>
<br>
</div>
</blockquote>
</div>
</div>
</div>

<br>
</blockquote></div><br clear="all"><br>-- <br><div dir="ltr" class="gmail_signature"><div dir="ltr"><div>Eric</div></div></div>