[squeak-dev] The Inbox: Graphics-ct.443.mcz

Eric Gade eric.gade at gmail.com
Fri Oct 9 15:16:49 UTC 2020


Hi Christoph,

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
compression
<https://github.com/pharo-project/pharo/blob/Pharo9.0/src/Graphics-Files/LzwGifEncoder.class.st>
/ decompression
<https://github.com/pharo-project/pharo/blob/Pharo9.0/src/Graphics-Files/LzwGifDecoder.class.st>
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!

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.

The need for composited frames also led me to ditch the
AnimatedGIFReadWriter entirely, and to fold in all functionality to
GIFReadWriter
<https://github.com/pharo-project/pharo/blob/Pharo9.0/src/Graphics-Files/GIFReadWriter.class.st>.
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.

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 here
<http://forum.world.st/GIFs-and-the-Color-black-td5120439.html>.

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.

I can't remember the reasoning for every choice I made here but definitely
hit me with any questions if you have them.

On Fri, Oct 9, 2020 at 11:00 AM Thiede, Christoph <
Christoph.Thiede at student.hpi.uni-potsdam.de> wrote:

> > And maybe double-check with the specification:
> https://www.w3.org/Graphics/GIF/spec-gif89a.txt
>
> This looks less funny to me, but you are right of course. :-)
>
> Best,
> Christoph
> ------------------------------
> *Von:* Squeak-dev <squeak-dev-bounces at lists.squeakfoundation.org> im
> Auftrag von Taeumel, Marcel
> *Gesendet:* Freitag, 9. Oktober 2020 16:53:44
> *An:* squeak-dev
> *Betreff:* Re: [squeak-dev] The Inbox: Graphics-ct.443.mcz
>
> Hi Christoph.
>
> > For a potentially relevant reference implementtion ...
> And maybe double-check with the specification:
> https://www.w3.org/Graphics/GIF/spec-gif89a.txt
>
> Best,
> Marcel
>
> Am 09.10.2020 16:15:36 schrieb commits at source.squeak.org <
> commits at source.squeak.org>:
> Christoph Thiede uploaded a new version of Graphics to project The Inbox:
> http://source.squeak.org/inbox/Graphics-ct.443.mcz
>
> ==================== Summary ====================
>
> Name: Graphics-ct.443
> Author: ct
> Time: 9 October 2020, 4:15:09.97236 pm
> UUID: 897346b7-ceaa-1a43-874f-3571d893309c
> Ancestors: Graphics-pre.439
>
> Adds basic support for storing an animated GIF file via
> AnimatedGIFReadWriter.
>
> 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") ...
> For a potentially relevant reference implementtion, I found this one, but
> I did not yet find the time apply it:
> https://gist.github.com/JimBobSquarePants/cac72c4e7d9f05f13ac9
>
> 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.
>
> =============== Diff against Graphics-pre.439 ===============
>
> Item was added:
> + ----- Method: AnimatedGIFReadWriter class>>exampleAnim (in category
> 'examples') -----
> + exampleAnim
> + "AnimatedGIFReadWriter exampleAnim"
> +
> + | extent center frames |
> + extent := 42 @ 42.
> + center := extent // 2.
> + frames := (2 to: center x - 1 by: 2) collect: [:r |
> + "Make a fancy anim without using Canvas - inefficient as hell"
> + | image |
> + image := ColorForm extent: extent depth: 8.
> + 0.0 to: 359.0 do: [:theta |
> + image
> + colorAt: (center + (Point r: r degrees: theta)) rounded
> + put: Color red].
> + image].
> +
> + ^ FileStream newFileNamed: 'anim.gif' do: [:stream |
> + self
> + putForms: frames
> + andDelays: (frames withIndexCollect: [:frame :index |
> + 10 + (index / frames size * 100)]) "Start fast, end slow"
> + onStream: stream]!
>
> Item was added:
> + ----- Method: AnimatedGIFReadWriter class>>putForms:andDelays:onStream:
> (in category 'image reading/writing') -----
> + putForms: forms andDelays: delays onStream: aWriteStream
> + "Store the given form sequence as an animation on the given stream."
> +
> + | writer canvas |
> + self assert: forms size = delays size.
> +
> + writer := self on: aWriteStream.
> + canvas := Form extent: forms first extent depth: 32.
> + [Cursor write showWhile: [
> + writer loopCount: -1.
> + forms with: delays do: [:form :delay |
> + writer delay: delay; flag: #todo. "ct: Does not work"
> + form displayOn: canvas at: 0 @ 0 rule: Form over.
> + writer nextPutImage: canvas]]]
> + ensure: [writer close].!
>
> Item was added:
> + ----- Method: AnimatedGIFReadWriter class>>putForms:onFileNamed: (in
> category 'image reading/writing') -----
> + putForms: formSequence onFileNamed: fileName
> + "Store the given form sequence as an animation on a file of the given
> name."
> +
> + FileStream newFileNamed: fileName do: [:stream |
> + self putForms: formSequence onStream: stream].!
>
> Item was added:
> + ----- Method: AnimatedGIFReadWriter class>>putForms:onStream: (in
> category 'image reading/writing') -----
> + putForms: forms onStream: aWriteStream
> + "Store the given form sequence as an animation on the given stream."
> +
> + ^ self
> + putForms: forms
> + andDelays: ((1 to: forms size) collect: [:i | 20])
> + onStream: aWriteStream!
>
> Item was changed:
> ----- Method: BMPReadWriter class>>readAllFrom: (in category 'testing')
> -----
> readAllFrom: fd
> "MessageTally spyOn:[BMPReadWriter readAllFrom: FileDirectory default]"
> fd fileNames do:[:fName|
> (fName endsWith: '.bmp') ifTrue:[
> + [Form fromBinaryStream: (fd readOnlyFileNamed: fName)] ifError: [].
> - [Form fromBinaryStream: (fd readOnlyFileNamed: fName)] on: Error
> do:[:nix].
> ].
> ].
> fd directoryNames do:[:fdName|
> self readAllFrom: (fd directoryNamed: fdName)
> ].!
>
> Item was removed:
> - ----- Method: GIFReadWriter class>>exampleAnim (in category 'examples')
> -----
> - exampleAnim
> - "GIFReadWriter exampleAnim"
> -
> - | writer extent center |
> - writer := GIFReadWriter on: (FileStream newFileNamed: 'anim.gif').
> - writer loopCount: 20. "Repeat 20 times"
> - writer delay: 10. "Wait 10/100 seconds"
> - extent := 42 at 42.
> - center := extent / 2.
> - Cursor write showWhile: [
> - [2 to: center x - 1 by: 2 do: [:r |
> - "Make a fancy anim without using Canvas - inefficient as hell"
> - | image |
> - image := ColorForm extent: extent depth: 8.
> - 0.0 to: 359.0 do: [:theta | image colorAt: (center + (Point r: r
> degrees: theta)) rounded put: Color red].
> - writer nextPutImage: image]
> - ] ensure: [writer close]].!
>
>
>
>

-- 
Eric
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20201009/be071d56/attachment.html>


More information about the Squeak-dev mailing list