[squeak-dev] The Trunk: Graphics-mt.515.mcz

Thiede, Christoph Christoph.Thiede at student.hpi.uni-potsdam.de
Wed May 4 17:51:30 UTC 2022


Very nice! Thank you, Eric and Marcel! :-)


Here is some additional feedback on the changes - I'm sorry I haven't collected these observations earlier:


  *   Importing of medium-sized GIFs has slowed down from e.g. 2 seconds to 13 seconds - is this slowdown really necessary? Should we maybe keep the old read writer in parallel for cases where performance matters?
  *   The busy cursor no longer appears when opening a GIF in the world via drag'n'drop.
  *   I'm attaching a few GIFs that are not played properly for me (either too fast or too slow so that I only can see the first frame). Maybe we can address them later.
  *   But very noticeably, I discovered so many GIFs on my disk that finally load and play properly in my image. :-)

Best,
Christoph

________________________________
Von: Squeak-dev <squeak-dev-bounces at lists.squeakfoundation.org> im Auftrag von commits at source.squeak.org <commits at source.squeak.org>
Gesendet: Mittwoch, 4. Mai 2022 11:57 Uhr
An: squeak-dev at lists.squeakfoundation.org; packages at lists.squeakfoundation.org
Betreff: [squeak-dev] The Trunk: Graphics-mt.515.mcz

Marcel Taeumel uploaded a new version of Graphics to project The Trunk:
http://source.squeak.org/trunk/Graphics-mt.515.mcz

==================== Summary ====================

Name: Graphics-mt.515
Author: mt
Time: 4 May 2022, 11:57:40.413048 am
UUID: f6dc9ac8-f9e4-1a4c-8614-6dfc9ea67e0e
Ancestors: Graphics-mt.514, Graphics-EG.453

Merges Graphics-EG.453

This commit represents a reimplementation of GIF file parsing. Previously, GIF decoding was not implemented correctly, and many GIFs would not display.

The new GIFReadWriter properly handles the GIF spec and treats all GIF images as (potentially) a series of image frames, using the utility class AnimatedImageFrame.

Additionally, GIFReadWriter breaks out the LZW encoding/decoding into utility classes, respectively named LzeGIFEncoder/Decoder.

I have gone ahead and deprecated AnimatedGIFReadWriter, since the reading of animated GIFs is now a normal part of the regular file reading process.

=============== Diff against Graphics-mt.514 ===============

Item was added:
+ ----- Method: AnimatedGIFReadWriter class>>basicNew (in category 'as yet unclassified') -----
+ basicNew
+        "Notify that the class is being deprecated."
+        self deprecated: 'This class is deprecated. Its functionality can be found in the regular GIFReadWriter class instead.'.
+        ^ super basicNew
+        !

Item was changed:
  ----- Method: AnimatedGIFReadWriter class>>typicalFileExtensions (in category 'image reading/writing') -----
  typicalFileExtensions
         "Answer a collection of file extensions (lowercase) which files that I can read might commonly have"
+        ^#()!
-        ^#('gif')!

Item was changed:
  ----- Method: AnimatedGIFReadWriter class>>wantsToHandleGIFs (in category 'image reading/writing') -----
  wantsToHandleGIFs
+        ^ false!
-        ^true!

Item was added:
+ Object subclass: #AnimatedImageFrame
+        instanceVariableNames: 'form delay disposal offset interlace'
+        classVariableNames: ''
+        poolDictionaries: ''
+        category: 'Graphics-Files'!
+
+ !AnimatedImageFrame commentStamp: '' prior: 0!
+ I am a single frame in a sequence of frames that comprise an animated image. I am designed for use by classes like AnimatedImageMorph and am created during the reading of some image files such as GIFs (see GIFReadWriter).
+
+ I contain a Form describing my image, a delay time in milliseconds that describes how long I should be displayed in a sequence of animated frames, and a disposal symbol that implementors can use when compositing myself in a sequence of frames.
+
+ See AnimatedImageMorph for examples of how I am used.!

Item was added:
+ ----- Method: AnimatedImageFrame>>defaultDelay (in category 'defaults') -----
+ defaultDelay
+        ^ 66!

Item was added:
+ ----- Method: AnimatedImageFrame>>delay (in category 'accessing') -----
+ delay
+        ^ delay!

Item was added:
+ ----- Method: AnimatedImageFrame>>delay: (in category 'accessing') -----
+ delay: aNumber
+        delay := aNumber!

Item was added:
+ ----- Method: AnimatedImageFrame>>disposal (in category 'accessing') -----
+ disposal
+        ^ disposal!

Item was added:
+ ----- Method: AnimatedImageFrame>>disposal: (in category 'accessing') -----
+ disposal: aSymbol
+        "Disposal must be one of:
+                #restoreBackground
+                #leaveCurrent
+                #restorePreviousState"
+        "({ #restoreBackground.
+                #leaveCurrent.
+                #restorePreviousState } includes: aSymbol) ifTrue: [
+                        disposal := aSymbol ]."
+        disposal := aSymbol!

Item was added:
+ ----- Method: AnimatedImageFrame>>form (in category 'accessing') -----
+ form
+        ^ form!

Item was added:
+ ----- Method: AnimatedImageFrame>>form: (in category 'accessing') -----
+ form: aForm
+        form := aForm!

Item was added:
+ ----- Method: AnimatedImageFrame>>initialize (in category 'initialization') -----
+ initialize
+        super initialize.
+        offset := 0 @ 0.
+        delay := self defaultDelay.
+        disposal := #otherDisposal.
+        interlace := false!

Item was added:
+ ----- Method: AnimatedImageFrame>>interlace (in category 'accessing') -----
+ interlace
+        ^ interlace!

Item was added:
+ ----- Method: AnimatedImageFrame>>interlace: (in category 'accessing') -----
+ interlace: aBoolean
+        interlace := aBoolean!

Item was added:
+ ----- Method: AnimatedImageFrame>>offset (in category 'accessing') -----
+ offset
+        ^ offset!

Item was added:
+ ----- Method: AnimatedImageFrame>>offset: (in category 'accessing') -----
+ offset: aPoint
+        "This represents the frame form's offset in the
+        parent image canvas"
+        offset := aPoint!

Item was changed:
  ImageReadWriter subclass: #GIFReadWriter
+        instanceVariableNames: 'width height bitsPerPixel colorPalette rowByteSize xpos ypos pass interlace transparentIndex localColorTable loopCount offset frames canvasWidth canvasHeight backgroundColorIndex comment'
-        instanceVariableNames: 'width height bitsPerPixel colorPalette rowByteSize xpos ypos pass interlace codeSize clearCode eoiCode freeCode maxCode prefixTable suffixTable remainBitCount bufByte bufStream transparentIndex mapOf32 localColorTable delay loopCount offset'
         classVariableNames: 'Extension ImageSeparator Terminator'
         poolDictionaries: ''
         category: 'Graphics-Files'!

+ !GIFReadWriter commentStamp: '' prior: 0!
+ I am GIFReadWriter.
+ I am a concrete ImageReadWriter.
- !GIFReadWriter commentStamp: '<historical>' prior: 0!
- Copyright (c) Kazuki Yasumatsu, 1995. All rights reserved.

+ Updated implementation of a GIF file (byte-level) decoder.
+
+ I implment a Stream-like behavior over a GIF image file, and can both read and write GIF files.
+
+ Previously, two classes distinguished between "still" and "animated" GIFs. However, the standard specifies that any GIF can have "frames" and be animated. This reimplementation treats this as normal.
+
+ See these links for more detailed information:
+
+  https://www.w3.org/Graphics/GIF/spec-gif89a.txt
+  https://en.wikipedia.org/wiki/GIF
+  http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
+
+ For writing GIF files, I take a collection of AnimatedImageFrame objects and write the appropriate headers, Graphics Control Extensions, and everything else needed for writing an animated GIF.
+
+ For reading GIF files, I take a binary filestream and set my own `frames` variable to be a collection of AnimatedImageFrames, which themselves contain decoded Forms and instructions for disposal, delay, etc.
+
+ NOTE: I make use of the LzwGifDecoder and LzwGifEncoder classes in order to encode/decode individual bitmap data for each image frame of the GIF.
+
+ See `GIFReadWriter exampleAnim` for more information.  !
- Used with permission.  Modified for use in Squeak.!

Item was changed:
  ----- Method: GIFReadWriter class>>exampleAnim (in category 'examples') -----
  exampleAnim
+        "This example writes out an animated gif of
+         a red circle"
-        "GIFReadWriter exampleAnim"

+        | writer extent center frameDelay |
+        writer := GIFReadWriter on: (FileStream fileNamed: 'anim.gif').
-        | writer extent center |
-        writer := GIFReadWriter on: (FileStream newFileNamed: 'anim.gif').
         writer loopCount: 20.           "Repeat 20 times"
+        frameDelay := 10.               "Wait 10/100 seconds"
-        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"
+                        | frame |
+                        frame := AnimatedImageFrame new
+                                delay: frameDelay;
+                                form: (ColorForm extent: extent depth: 8).
+                        0.0 to: 359.0 do: [:theta | frame form colorAt: (center + (Point r: r degrees: theta)) rounded put: Color red].
+                        writer nextPutFrame: frame ]
-                        | 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]].!

Item was added:
+ ----- Method: GIFReadWriter class>>formsFromFileNamed: (in category 'as yet unclassified') -----
+ formsFromFileNamed: aFile
+        ^ (self on: aFile asDirectoryEntry readStream binary)
+                        readHeader;
+                        readBody;
+                        yourself!

Item was added:
+ ----- Method: GIFReadWriter class>>formsFromStream: (in category 'as yet unclassified') -----
+ formsFromStream: aBinaryStream
+        ^ (self on: aBinaryStream)
+                readHeader;
+                readBody;
+                yourself!

Item was changed:
+ ----- Method: GIFReadWriter class>>initialize (in category 'initialization') -----
- ----- Method: GIFReadWriter class>>initialize (in category 'class initialization') -----
  initialize
         "GIFReadWriter initialize"
-
         ImageSeparator := $, asInteger.
         Extension := $!! asInteger.
+        Terminator := $; asInteger!
-        Terminator := $; asInteger.
- !

Item was changed:
  ----- Method: GIFReadWriter class>>typicalFileExtensions (in category 'image reading/writing') -----
  typicalFileExtensions
         "Answer a collection of file extensions (lowercase) which files that I can
         read might commonly have"

+        ^ self allSubclasses
+                detect: [ :cls | cls wantsToHandleGIFs ]
+                ifFound: [ #() ]
+                ifNone: [
+                        "if none of my subclasses wants , then i''ll have to do"
+                        #('gif') ]!
-        self
-                allSubclasses detect: [:cls | cls wantsToHandleGIFs ]
-                                         ifNone: ["if none of my subclasses wants , then i''ll have to do"
-                                                        ^ #('gif' )].
-        ^ #( )!

Item was changed:
  ----- Method: GIFReadWriter class>>wantsToHandleGIFs (in category 'image reading/writing') -----
  wantsToHandleGIFs
+        ^ true!
-        ^ false!

Item was added:
+ ----- Method: GIFReadWriter>>backgroundColor (in category 'accessing') -----
+ backgroundColor
+        backgroundColorIndex ifNotNil: [
+                colorPalette ifNotNil: [
+                        ^ colorPalette at: backgroundColorIndex + 1]].
+        ^ Color transparent.!

Item was added:
+ ----- Method: GIFReadWriter>>canvasHeight (in category 'accessing') -----
+ canvasHeight
+        ^ canvasHeight!

Item was added:
+ ----- Method: GIFReadWriter>>canvasHeight: (in category 'accessing') -----
+ canvasHeight: aNumber
+        canvasHeight := aNumber!

Item was added:
+ ----- Method: GIFReadWriter>>canvasWidth (in category 'accessing') -----
+ canvasWidth
+        ^ canvasWidth!

Item was added:
+ ----- Method: GIFReadWriter>>canvasWidth: (in category 'accessing') -----
+ canvasWidth: aNumber
+        canvasWidth := aNumber!

Item was removed:
- ----- Method: GIFReadWriter>>checkCodeSize (in category 'private') -----
- checkCodeSize
-        (freeCode > maxCode and: [codeSize < 12])
-                ifTrue:
-                        [codeSize := codeSize + 1.
-                        maxCode := (1 bitShift: codeSize) - 1]!

Item was added:
+ ----- Method: GIFReadWriter>>close (in category 'stream access') -----
+ close
+        "A read close"
+        ^super close!

Item was added:
+ ----- Method: GIFReadWriter>>delays (in category 'accessing') -----
+ delays
+        "Respond with an ordered collection of Frame delay values"
+        ^ frames collect: [ :frame | frame delay ]!

Item was removed:
- ----- Method: GIFReadWriter>>fillBuffer (in category 'private-packing') -----
- fillBuffer
-        | packSize |
-        packSize := self next.
-        bufStream := ReadStream on: (self next: packSize)!

Item was removed:
- ----- Method: GIFReadWriter>>flushBits (in category 'private-bits access') -----
- flushBits
-        remainBitCount = 0 ifFalse:
-                [self nextBytePut: bufByte.
-                remainBitCount := 0].
-        self flushBuffer!

Item was removed:
- ----- Method: GIFReadWriter>>flushBuffer (in category 'private-packing') -----
- flushBuffer
-        bufStream isEmpty ifTrue: [^self].
-        self nextPut: bufStream size.
-        self nextPutAll: bufStream contents.
-        bufStream := WriteStream on: (ByteArray new: 256)!

Item was removed:
- ----- Method: GIFReadWriter>>flushCode (in category 'private-encoding') -----
- flushCode
-        self flushBits!

Item was added:
+ ----- Method: GIFReadWriter>>form (in category 'accessing') -----
+ form
+        "By default, answer with the first Form available in the
+        ImageFrames collection. If there are not any frames, answer nil"
+        frames ifNil: [ ^ nil ].
+        frames ifEmpty: [ ^ nil ].
+        ^ frames first form.!

Item was added:
+ ----- Method: GIFReadWriter>>forms (in category 'accessing') -----
+ forms
+        frames ifNil: [ ^ nil ].
+        ^ frames collect: [ :f | f form ].!

Item was added:
+ ----- Method: GIFReadWriter>>frames (in category 'accessing') -----
+ frames
+        ^ frames!

Item was added:
+ ----- Method: GIFReadWriter>>frames: (in category 'accessing') -----
+ frames: aCollectionOfImageFrames
+        "Set the receiver's underlying collection of
+        ImageFrame objects. Used when attempting to write
+        out GIF images"
+        frames := aCollectionOfImageFrames!

Item was added:
+ ----- Method: GIFReadWriter>>isAnimated (in category 'testing') -----
+ isAnimated
+        frames ifNil: [ ^ false ].
+        ^ frames size > 1!

Item was changed:
  ----- Method: GIFReadWriter>>loopCount: (in category 'accessing') -----
  loopCount: aNumber
         "Set looping. This must be done before any image is written!!"
         loopCount := aNumber!

Item was removed:
- ----- Method: GIFReadWriter>>nextBits (in category 'private-bits access') -----
- nextBits
-        | integer readBitCount shiftCount byte |
-        integer := 0.
-        remainBitCount = 0
-                ifTrue:
-                        [readBitCount := 8.
-                        shiftCount := 0]
-                ifFalse:
-                        [readBitCount := remainBitCount.
-                        shiftCount := remainBitCount - 8].
-        [readBitCount < codeSize]
-                whileTrue:
-                        [byte := self nextByte.
-                        byte == nil ifTrue: [^eoiCode].
-                        integer := integer + (byte bitShift: shiftCount).
-                        shiftCount := shiftCount + 8.
-                        readBitCount := readBitCount + 8].
-        (remainBitCount := readBitCount - codeSize) = 0
-                ifTrue: [byte := self nextByte]
-                ifFalse:        [byte := self peekByte].
-        byte == nil ifTrue: [^eoiCode].
-        ^(integer + (byte bitShift: shiftCount)) bitAnd: maxCode!

Item was removed:
- ----- Method: GIFReadWriter>>nextBitsPut: (in category 'private-bits access') -----
- nextBitsPut: anInteger
-        | integer writeBitCount shiftCount |
-        shiftCount := 0.
-        remainBitCount = 0
-                ifTrue:
-                        [writeBitCount := 8.
-                        integer := anInteger]
-                ifFalse:
-                        [writeBitCount := remainBitCount.
-                        integer := bufByte + (anInteger bitShift: 8 - remainBitCount)].
-        [writeBitCount < codeSize]
-                whileTrue:
-                        [self nextBytePut: ((integer bitShift: shiftCount) bitAnd: 255).
-                        shiftCount := shiftCount - 8.
-                        writeBitCount := writeBitCount + 8].
-        (remainBitCount := writeBitCount - codeSize) = 0
-                ifTrue: [self nextBytePut: (integer bitShift: shiftCount)]
-                ifFalse: [bufByte := integer bitShift: shiftCount].
-        ^anInteger!

Item was removed:
- ----- Method: GIFReadWriter>>nextByte (in category 'private-packing') -----
- nextByte
-        bufStream atEnd
-                ifTrue:
-                        [self atEnd ifTrue: [^nil].
-                        self fillBuffer].
-        ^bufStream next!

Item was removed:
- ----- Method: GIFReadWriter>>nextBytePut: (in category 'private-packing') -----
- nextBytePut: aByte
-        bufStream nextPut: aByte.
-        bufStream size >= 254 ifTrue: [self flushBuffer]!

Item was changed:
  ----- Method: GIFReadWriter>>nextImage (in category 'accessing') -----
  nextImage
+        "This method ensures older compatibility with ImageReadWriter.
+        We respond with the Form corresponding to the *first image* on
+        the receiver's read byte stream"
+        self
+                readHeader;
+                readBody.
+        ^ self form.!
-        "Read in the next GIF image from the stream."
-
-        | f thisImageColorTable |
-
-        localColorTable := nil.
-        self readHeader.
-        f := self readBody.
-        self close.
-        f == nil ifTrue: [^ self error: 'corrupt GIF file' translated].
-
-        thisImageColorTable := localColorTable ifNil: [colorPalette].
-        transparentIndex ifNotNil: [
-                transparentIndex + 1 > thisImageColorTable size ifTrue: [
-                        thisImageColorTable := thisImageColorTable
-                                forceTo: transparentIndex + 1
-                                paddingWith: Color white
-                ].
-                thisImageColorTable at: transparentIndex + 1 put: Color transparent
-        ].
-        f colors: thisImageColorTable.
-        ^ f
- !

Item was added:
+ ----- Method: GIFReadWriter>>nextPutFrame: (in category 'accessing') -----
+ nextPutFrame: anAnimatedImageFrame
+        "Given the current settings, write the bytes onto the
+        output stream for the given ImageFrame and its form"
+        | aForm reduced tempForm tempFrame |
+
+        aForm := anAnimatedImageFrame form copy.
+        aForm unhibernate.
+        aForm depth > 8 ifTrue:[
+                reduced := aForm colorReduced.  "minimize depth"
+                reduced depth > 8 ifTrue: [
+                        "Not enough color space; do it the hard way."
+                        reduced := reduced asFormOfDepth: 8].
+        ] ifFalse:[reduced := aForm].
+        reduced depth < 8 ifTrue: [
+                "writeBitData: expects depth of 8"
+                tempForm := reduced class extent: reduced extent depth: 8.
+                (reduced isColorForm) ifTrue:[
+                        tempForm
+                                copyBits: reduced boundingBox
+                                from: reduced at: 0 at 0
+                                clippingBox: reduced boundingBox
+                                rule: Form over
+                                fillColor: nil
+                                map: nil.
+                        tempForm colors: reduced colors.
+                ] ifFalse: [reduced displayOn: tempForm].
+                reduced := tempForm.
+        ].
+        (reduced isColorForm) ifTrue:[
+                (reduced colorsUsed includes: Color transparent) ifTrue: [
+                        transparentIndex := (reduced colors indexOf: Color transparent) - 1.
+                ]
+        ] ifFalse: [transparentIndex := nil].
+        width := reduced width.
+        height := reduced height.
+        bitsPerPixel := reduced depth.
+        colorPalette := reduced colormapIfNeededForDepth: 32.
+        interlace := false.
+        tempFrame := AnimatedImageFrame new
+                form: reduced;
+                offset: anAnimatedImageFrame offset;
+                delay: anAnimatedImageFrame delay;
+                disposal: anAnimatedImageFrame disposal.
+        self writeHeader.
+        self writeFrameHeader: tempFrame.
+        self writeBitData: reduced bits.!

Item was changed:
  ----- Method: GIFReadWriter>>nextPutImage: (in category 'accessing') -----
  nextPutImage: aForm
+        "Given the current settings, write the bytes onto the
+        output stream for the given ImageFrame and its form"
+        | reduced tempForm tempFrame |
+
-
-        | reduced tempForm |
         aForm unhibernate.
         aForm depth > 8 ifTrue:[
                 reduced := aForm colorReduced.  "minimize depth"
                 reduced depth > 8 ifTrue: [
                         "Not enough color space; do it the hard way."
                         reduced := reduced asFormOfDepth: 8].
         ] ifFalse:[reduced := aForm].
         reduced depth < 8 ifTrue: [
                 "writeBitData: expects depth of 8"
+                tempForm := reduced class extent: reduced extent depth: 8.
-                tempForm := reduced species extent: reduced extent depth: 8.
                 (reduced isColorForm) ifTrue:[
                         tempForm
                                 copyBits: reduced boundingBox
                                 from: reduced at: 0 at 0
                                 clippingBox: reduced boundingBox
                                 rule: Form over
                                 fillColor: nil
                                 map: nil.
                         tempForm colors: reduced colors.
                 ] ifFalse: [reduced displayOn: tempForm].
                 reduced := tempForm.
         ].
         (reduced isColorForm) ifTrue:[
                 (reduced colorsUsed includes: Color transparent) ifTrue: [
                         transparentIndex := (reduced colors indexOf: Color transparent) - 1.
                 ]
         ] ifFalse: [transparentIndex := nil].
         width := reduced width.
         height := reduced height.
         bitsPerPixel := reduced depth.
         colorPalette := reduced colormapIfNeededForDepth: 32.
         interlace := false.
+        tempFrame := AnimatedImageFrame new
+                form: reduced;
+                offset: reduced offset.
         self writeHeader.
+        self writeFrameHeader: tempFrame.
+        self writeBitData: reduced bits.!
-        self writeBitData: reduced bits.
- !

Item was removed:
- ----- Method: GIFReadWriter>>peekByte (in category 'private-packing') -----
- peekByte
-        bufStream atEnd
-                ifTrue:
-                        [self atEnd ifTrue: [^nil].
-                        self fillBuffer].
-        ^bufStream peek!

Item was added:
+ ----- Method: GIFReadWriter>>processColorsFor: (in category 'private - decoding') -----
+ processColorsFor: anImageFrame
+        "Colors can only be mapped after the GCE has been evaluated
+        for a given image frame. We perform this action using either
+        the local or global color table for this frame's form"
+        | colorTable |
+        colorTable := localColorTable ifNil: [ colorPalette ].
+
+        "Use a copy so we don't mess up the global color table as we parse"
+        colorTable := colorTable copyFrom: 1 to: colorTable size.
+
+        transparentIndex
+                ifNotNil: [
+                        transparentIndex + 1 > colorTable size
+                                ifTrue: [
+                                        colorTable := colorTable
+                                                                                forceTo: transparentIndex + 1
+                                                                                paddingWith: Color white ].
+                                colorTable
+                                        at: transparentIndex + 1
+                                        put: Color transparent ].
+        anImageFrame form colors: colorTable.!

Item was added:
+ ----- Method: GIFReadWriter>>readApplicationExtension (in category 'private - decoding') -----
+ readApplicationExtension
+        "Uses the underlying stream to read a so-called
+        Application Extension to the GIF Image. These extensions
+        are at the whole file -- not individual frame like a GCE --
+        level. It appears the only kind widely used is the NETSCAPE
+        extension for determining the number of times an animated
+        GIF should loop."
+        | bytesFollow appName appAuthCode caughtInfo numSubBlocks loopVal1 loopVal2 |
+        "How many bytes before data begins?
+        Usually 11"
+        bytesFollow := self next.
+        appName := (String streamContents: [ :s |
+                1 to: 8 do: [ :num |
+                        s
+                                nextPut: self next asCharacter ] ]).
+        appAuthCode := (String streamContents: [ :s |
+                1 to: 3 do: [ :num |
+                        s
+                                nextPut: self next asCharacter ] ]).
+        caughtInfo := (appName size + appAuthCode size).
+        caughtInfo = bytesFollow ifFalse: [
+                (bytesFollow = caughtInfo) timesRepeat: [
+                        self next ] ].
+        numSubBlocks := self next.
+        appName = 'NETSCAPE'
+                ifTrue: [
+                        self next. "Data sub-block index (always 1)"
+                        "If it's the NETSCAPE extension, the next
+                        byte will set the loopCount. This is stored in
+                        a 2-byte lo-hi unsigned format"
+                        loopVal1 := self next.
+                        loopVal2 := self next.
+                        loopCount := (loopVal2 * 256) + loopVal1.
+                        self next = 0 ifFalse: [ ^ self error: 'Corrupt NETSCAPE Application Block' ].
+                        ^ self ].
+
+        "For now we ignore Application Extensions
+        that are not the NETSCAPE kind"
+        [ numSubBlocks = 0 ] whileFalse: [
+                self next: numSubBlocks.
+                numSubBlocks := self next ].
+        !

Item was removed:
- ----- Method: GIFReadWriter>>readBitData (in category 'private-decoding') -----
- readBitData
-        "using modified Lempel-Ziv Welch algorithm."
-
-        | outCodes outCount bitMask initCodeSize code curCode oldCode inCode finChar i bytes f c packedBits hasLocalColor localColorSize maxOutCodes |
-
-        maxOutCodes := 4096.
-        offset := self readWord at self readWord. "Image Left at Image Top"
-        width := self readWord.
-        height := self readWord.
-
-        "---
-        Local Color Table Flag        1 Bit
-        Interlace Flag                1 Bit
-        Sort Flag                     1 Bit
-        Reserved                      2 Bits
-        Size of Local Color Table     3 Bits
-        ----"
-        packedBits := self next.
-        interlace := (packedBits bitAnd: 16r40) ~= 0.
-        hasLocalColor := (packedBits bitAnd: 16r80) ~= 0.
-        localColorSize := 1 bitShift: ((packedBits bitAnd: 16r7) + 1).
-        hasLocalColor ifTrue: [localColorTable := self readColorTable: localColorSize].
-
-        pass := 0.
-        xpos := 0.
-        ypos := 0.
-        rowByteSize := ((width + 3) // 4) * 4.
-        remainBitCount := 0.
-        bufByte := 0.
-        bufStream := ReadStream on: ByteArray new.
-
-        outCodes := ByteArray new: maxOutCodes + 1.
-        outCount := 0.
-        bitMask := (1 bitShift: bitsPerPixel) - 1.
-        prefixTable := Array new: 4096.
-        suffixTable := Array new: 4096.
-
-        initCodeSize := self next.
-
-        self setParameters: initCodeSize.
-        bitsPerPixel > 8 ifTrue: [^self error: ('never heard of a GIF that deep (depth = {1})' translated format: {bitsPerPixel})].
-        bytes := ByteArray new: rowByteSize * height.
-        [(code := self readCode) = eoiCode] whileFalse:
-                [code = clearCode
-                        ifTrue:
-                                [self setParameters: initCodeSize.
-                                curCode := oldCode := code := self readCode.
-                                finChar := curCode bitAnd: bitMask.
-                                "Horrible hack to avoid running off the end of the bitmap.  Seems to cure problem reading some gifs!!? tk 6/24/97 20:16"
-                                xpos = 0 ifTrue: [
-                                                ypos < height ifTrue: [
-                                                        bytes at: (ypos * rowByteSize) + xpos + 1 put: finChar]]
-                                        ifFalse: [bytes at: (ypos * rowByteSize) + xpos + 1 put: finChar].
-                                self updatePixelPosition]
-                        ifFalse:
-                                [curCode := inCode := code.
-                                curCode >= freeCode ifTrue:
-                                        [curCode := oldCode.
-                                        outCodes at: (outCount := outCount + 1) put: finChar].
-                                [curCode > bitMask] whileTrue:
-                                        [outCount > maxOutCodes
-                                                ifTrue: [^self error: ('corrupt GIF file ({1})' translated format: {'OutCount'})].
-                                        outCodes at: (outCount := outCount + 1)
-                                                put: (suffixTable at: curCode + 1).
-                                        curCode := prefixTable at: curCode + 1].
-                                finChar := curCode bitAnd: bitMask.
-                                outCodes at: (outCount := outCount + 1) put: finChar.
-                                i := outCount.
-                                [i > 0] whileTrue:
-                                        ["self writePixel: (outCodes at: i) to: bits"
-                                        bytes at: (ypos * rowByteSize) + xpos + 1 put: (outCodes at: i).
-                                        self updatePixelPosition.
-                                        i := i - 1].
-                                outCount := 0.
-                                prefixTable at: freeCode + 1 put: oldCode.
-                                suffixTable at: freeCode + 1 put: finChar.
-                                oldCode := inCode.
-                                freeCode := freeCode + 1.
-                                self checkCodeSize]].
-        prefixTable := suffixTable := nil.
-
-        f := ColorForm extent: width at height depth: 8.
-        f bits copyFromByteArray: bytes.
-        "Squeak can handle depths 1, 2, 4, and 8"
-        bitsPerPixel > 4 ifTrue: [^ f].
-        "reduce depth to save space"
-        c := ColorForm extent: width at height
-                depth: (bitsPerPixel = 3 ifTrue: [4] ifFalse: [bitsPerPixel]).
-        f displayOn: c.
-        ^ c!

Item was added:
+ ----- Method: GIFReadWriter>>readBitDataOnFrame: (in category 'private - decoding') -----
+ readBitDataOnFrame: aFrame
+        "using modified Lempel-Ziv Welch algorithm."
+        | initCodeSize  packedBits hasLocalColor localColorSize maxOutCodes decoder c  bytes |
+        maxOutCodes := 4096.
+        offset := self readWord @ self readWord.        "Image Left at Image Top"
+        width := self readWord.
+        height := self readWord.
+        "---
+        Local Color Table Flag        1 Bit
+        Interlace Flag                1 Bit
+        Sort Flag                     1 Bit
+        Reserved                      2 Bits
+        Size of Local Color Table     3 Bits
+        ----"
+        packedBits := self next.
+        interlace := (packedBits bitAnd: 64) ~= 0.
+        aFrame interlace: interlace.
+        hasLocalColor := (packedBits bitAnd: 128) ~= 0.
+        localColorSize := 1 bitShift: (packedBits bitAnd: 7) + 1.
+        hasLocalColor
+                ifTrue: [
+                        localColorTable := self readColorTable: localColorSize ]
+                ifFalse: [ localColorTable := nil ].
+        pass := 0.
+        xpos := 0.
+        ypos := 0.
+        rowByteSize := (width + 3) // 4 * 4.
+        bytes := (ByteArray new: rowByteSize * height).
+
+        initCodeSize := self next.
+
+        c := ColorForm
+                extent: width at height
+                depth: 8.
+
+        decoder := LzwGifDecoder new.
+        decoder
+                codeStream: stream;
+                minimumCodeSize: initCodeSize;
+                maxCode: maxOutCodes;
+                onDecodedBit: [ :bit |
+                        bytes
+                                at: (ypos * rowByteSize + xpos + 1)
+                                put: bit.
+                        self updatePixelPosition ].
+        decoder decode.
+        c bits copyFromByteArray: bytes.
+        ^ c!

Item was changed:
+ ----- Method: GIFReadWriter>>readBody (in category 'private - decoding') -----
- ----- Method: GIFReadWriter>>readBody (in category 'private-decoding') -----
  readBody
+        "Read the GIF blocks. Modified to return a frame."
+        | block frame |
+        frame := nil.
+        frames := OrderedCollection new.
+        [ stream atEnd ] whileFalse: [
-        "Read the GIF blocks. Modified to return a form.  "
-
-        | form extype block blocksize packedFields delay1 |
-        form := nil.
-        [stream atEnd] whileFalse: [
                 block := self next.
+
+                "If we have reached the terminator byte, return."
+                block = Terminator ifTrue: [ ^ frame ].
+                block = ImageSeparator
+                        ifTrue: [
+                                frame ifNil: [ frame := AnimatedImageFrame new ].
+                                frame form: (self readBitDataOnFrame: frame). "Adjusting message for testing"
+                                frame offset: offset. "Set from instance var, which is set in readBitData"
+                                frame form offset: offset. "Set the offset on the underlying Form as well"
+
+                                frames add: frame.
+                                self processColorsFor: frame.
+                                frame := nil. ]
+                        ifFalse:
+                                [ "If it's not actual image data, perhaps
+                                        it's an Extension of some kind (there can be several)"
+                                        block = Extension
+                                                ifTrue: [
+                                                        frame ifNil: [ frame := AnimatedImageFrame new ].
+                                                        self readExtensionBlock: block withFrame: frame ]
+                                                ifFalse: [ ^ self error: 'Unknown Bytes!!' ] ]
+                ].
+        ^ frames.!
-                block = Terminator ifTrue: [^ form].
-                block = ImageSeparator ifTrue: [
-                        form isNil
-                                ifTrue: [form := self readBitData]
-                                ifFalse: [self skipBitData].
-                ] ifFalse: [
-                        block = Extension
-                                ifFalse: [^ form "^ self error: 'Unknown block type'"].
-                        "Extension block"
-                        extype := self next.    "extension type"
-                        extype = 16rF9 ifTrue: [  "graphics control"
-                                self next = 4 ifFalse: [^ form "^ self error: 'corrupt GIF file'"].
-                                "====
-                                Reserved                      3 Bits
-                                Disposal Method               3 Bits
-                                User Input Flag               1 Bit
-                                Transparent Color Flag        1 Bit
-                                ==="
-
-                                packedFields := self next.
-                                delay1 := self next.    "delay time 1"
-                                delay := (self next*256 + delay1) *10.   "delay time 2"
-                                transparentIndex := self next.
-                                (packedFields bitAnd: 1) = 0 ifTrue: [transparentIndex := nil].
-                                self next = 0 ifFalse: [^ form "^ self error: 'corrupt GIF file'"].
-                        ] ifFalse: [
-                                "Skip blocks"
-                                [(blocksize := self next) > 0]
-                                        whileTrue: [
-                                                "Read the block and ignore it and eat the block terminator"
-                                                self next: blocksize]]]].
-                ^nil!

Item was removed:
- ----- Method: GIFReadWriter>>readCode (in category 'private-decoding') -----
- readCode
-        ^self nextBits!

Item was changed:
+ ----- Method: GIFReadWriter>>readColorTable: (in category 'private - decoding') -----
+ readColorTable: numberOfEntries
- ----- Method: GIFReadWriter>>readColorTable: (in category 'private-decoding') -----
- readColorTable: numberOfEntries
-
         | array r g b |
-
         array := Array new: numberOfEntries.
+        1
+                to: array size
+                do:
+                        [ :i |
+                        r := self next.
+                        g := self next.
+                        b := self next.
+                        array
+                                at: i
+                                put: (Color
+                                                r: r
+                                                g: g
+                                                b: b
+                                                range: 255) ].
+        ^ array!
-        1 to: array size do: [ :i |
-                r := self next.
-                g := self next.
-                b := self next.
-                array at: i put: (Color r: r g: g b: b range: 255)
-        ].
-        ^array!

Item was added:
+ ----- Method: GIFReadWriter>>readCommentExtension (in category 'private - decoding') -----
+ readCommentExtension
+        | blockTerminator |
+        blockTerminator := self next.
+        blockTerminator > 0
+                ifTrue: [ comment := self next: blockTerminator.
+                        blockTerminator := self next ].
+        blockTerminator = 0
+                ifFalse: [ ^ self error: 'Invalid Block Terminator' ]!

Item was added:
+ ----- Method: GIFReadWriter>>readDisposal: (in category 'private - decoding') -----
+ readDisposal: aPackedByte
+        "Read the three-bit disposal flag from
+        the packed byte in the Graphic Control Extension block.
+        Disposal is three-bits with the following codes:
+         |0 0 0 [0 0 0] 0 0|
+        1 => leave current frame and draw on top of it (#leaveCurrent)
+        2 => Restore to background color (#restoreBackground)
+        3 => Restore to state before current frame was drawn (#restorePrevState)"
+        | least middle both |
+        (both := (aPackedByte bitAnd: 12) = 12).
+        both ifTrue: [ ^ #restorePrevState ].
+
+        least := (aPackedByte bitAnd: 4) = 4.
+        least ifTrue: [ ^ #leaveCurrent ].
+
+        middle := (aPackedByte bitAnd: 8) = 8.
+        middle ifTrue: [ ^ #restoreBackground ].
+
+        ^ #otherDisposal
+        !

Item was added:
+ ----- Method: GIFReadWriter>>readExtensionBlock:withFrame: (in category 'private - decoding') -----
+ readExtensionBlock: aGifBlock withFrame: anImageFrame
+        "Determine which type of extension block we are
+        looking at. The most common is the Graphic Control Extension (GCE)
+        which tells us information about the image frame, including delays
+        offsets in the canvas, and how to dispose of the frame in animation"
+        | extensionType packedByte delayByte1 delayByte2 |
+        extensionType := self next.
+
+        "255 is an Application Extension.
+         This seems to always be the NETSCAPE
+         extension, which has looping information.
+        This extension does not affect individual frames,
+        but rather sets the loopCount for the whole image"
+        extensionType = 255 ifTrue: [
+                ^ self readApplicationExtension ].
+
+
+        "249 Corresponds to the GCE"
+        extensionType = 249 ifTrue: [
+                self next = 4 ifFalse: [ ^ self "The GIF is likely corrupt in this case" ].
+                "====
+                Reserved                      3 Bits (Ignore)
+                Disposal Method               3 Bits
+                User Input Flag               1 Bit  (Ignore)
+                Transparent Color Flag        1 Bit  (Need to Implement)
+                ==="
+                packedByte := self next.
+                delayByte1 := self next.
+                delayByte2 := self next.
+                transparentIndex := self next.
+                (packedByte bitAnd: 1) = 0 "Changed to see if other endian is the real end..."
+                        ifTrue: [ transparentIndex := nil ].
+                anImageFrame
+                        disposal: (self readDisposal: packedByte);
+                        "Delay time is stored as 2 bytes unsigned"
+                        delay: (delayByte2 * 256 + delayByte1) * 10.
+                self next = 0 ifFalse: [ ^ self error: 'Corrupt GCE Block!!' ].
+                ^ self ].
+
+        extensionType = 254 ifTrue: [
+                ^ self readCommentExtension ].
+
+        "If you get to this point, we don't know the Extension Type"
+        ^ self error: 'Unknown GIF Extension: ',(extensionType asString).!

Item was changed:
+ ----- Method: GIFReadWriter>>readHeader (in category 'private - decoding') -----
- ----- Method: GIFReadWriter>>readHeader (in category 'private-decoding') -----
  readHeader
         | is89 byte hasColorMap |
+        (self hasMagicNumber: 'GIF87a' asByteArray)
+                ifTrue: [ is89 := false ]
+                ifFalse:
+                        [ (self hasMagicNumber: 'GIF89a' asByteArray)
+                                ifTrue: [ is89 := true ]
+                                ifFalse: [ ^ self error: 'This does not appear to be a GIF file' translated ] ].
+        "Width and Height for whole canvas, not
+        just an invididual frame/form"
+        canvasWidth := self readWord.
+        canvasHeight := self readWord.
-        (self hasMagicNumber: 'GIF87a' asByteArray)
-                ifTrue: [is89 := false]
-                ifFalse: [(self hasMagicNumber: 'GIF89a' asByteArray)
-                        ifTrue: [is89 := true]
-                        ifFalse: [^ self error: 'This does not appear to be a GIF file' translated]].
-        self readWord.  "skip Screen Width"
-        self readWord.  "skip Screen Height"
         byte := self next.
+        hasColorMap := (byte bitAnd: 128) ~= 0.
-        hasColorMap := (byte bitAnd: 16r80) ~= 0.
         bitsPerPixel := (byte bitAnd: 7) + 1.
+        backgroundColorIndex := self next.
+        self next ~= 0 ifTrue:
+                [ is89 ifFalse: [ ^ self error: 'corrupt GIF file (screen descriptor)' ] ].
+        hasColorMap
+                ifTrue: [ colorPalette := self readColorTable: (1 bitShift: bitsPerPixel) ]
+                ifFalse:
+                        [ colorPalette := nil   "Palette monochromeDefault" ]!
-        byte := self next.      "skip background color."
-        self next ~= 0
-                ifTrue: [is89
-                        ifFalse: [^self error: ('corrupt GIF file ({1})' translated format: {'screen descriptor'})]].
-        hasColorMap
-                ifTrue:
-                        [colorPalette := self readColorTable: (1 bitShift: bitsPerPixel)]
-                ifFalse:
-                        ["Transcript cr; show: 'GIF file does not have a color map.'."
-                        colorPalette := nil "Palette monochromeDefault"].!

Item was changed:
+ ----- Method: GIFReadWriter>>readWord (in category 'private - decoding') -----
- ----- Method: GIFReadWriter>>readWord (in category 'private-decoding') -----
  readWord
         ^self next + (self next bitShift: 8)!

Item was removed:
- ----- Method: GIFReadWriter>>setParameters: (in category 'private') -----
- setParameters: initCodeSize
-        clearCode := 1 bitShift: initCodeSize.
-        eoiCode := clearCode + 1.
-        freeCode := clearCode + 2.
-        codeSize := initCodeSize + 1.
-        maxCode := (1 bitShift: codeSize) - 1!

Item was changed:
  ----- Method: GIFReadWriter>>setStream: (in category 'accessing') -----
+ setStream: aStream
- setStream: aStream
         "Feed it in from an existing source"
         stream := aStream!

Item was removed:
- ----- Method: GIFReadWriter>>skipBitData (in category 'private-decoding') -----
- skipBitData
-        | misc blocksize |
-        self readWord.  "skip Image Left"
-        self readWord.  "skip Image Top"
-        self readWord.  "width"
-        self readWord.  "height"
-        misc := self next.
-        (misc bitAnd: 16r80) = 0 ifFalse: [ "skip colormap"
-                1 to: (1 bitShift: (misc bitAnd: 7) + 1) do: [:i |
-                        self next; next; next]].
-        self next.  "minimum code size"
-        [(blocksize := self next) > 0]
-                whileTrue: [self next: blocksize]!

Item was changed:
  ----- Method: GIFReadWriter>>understandsImageFormat (in category 'accessing') -----
  understandsImageFormat
         ^('abc' collect: [:x | stream next asCharacter]) = 'GIF'!

Item was changed:
  ----- Method: GIFReadWriter>>updatePixelPosition (in category 'private') -----
  updatePixelPosition
+        (xpos := xpos + 1) >= width ifFalse: [ ^ self ].
-        (xpos := xpos + 1) >= width ifFalse: [^self].
         xpos := 0.
+        interlace ifFalse:
+                [ ypos := ypos + 1.
+                ^ self ].
+        pass = 0 ifTrue:
+                [ (ypos := ypos + 8) >= height ifTrue:
+                        [ pass := pass + 1.
+                        ypos := 4 ].
+                ^ self ].
+        pass = 1 ifTrue:
+                [ (ypos := ypos + 8) >= height ifTrue:
+                        [ pass := pass + 1.
+                        ypos := 2 ].
+                ^ self ].
+        pass = 2 ifTrue:
+                [ (ypos := ypos + 4) >= height ifTrue:
+                        [ pass := pass + 1.
+                        ypos := 1 ].
+                ^ self ].
+        pass = 3 ifTrue:
+                [ ypos := ypos + 2.
+                ^ self ].
+        ^ self caseError!
-        interlace
-                ifFalse: [ypos := ypos + 1. ^self].
-        pass = 0 ifTrue:
-                [(ypos := ypos + 8) >= height
-                        ifTrue:
-                                [pass := pass + 1.
-                                ypos := 4].
-                ^self].
-        pass = 1 ifTrue:
-                [(ypos := ypos + 8) >= height
-                        ifTrue:
-                                [pass := pass + 1.
-                                ypos := 2].
-                ^self].
-        pass = 2 ifTrue:
-                [(ypos := ypos + 4) >= height
-                        ifTrue:
-                                [pass := pass + 1.
-                                ypos := 1].
-                ^self].
-        pass = 3 ifTrue:
-                [ypos := ypos + 2.
-                ^self].
-
-        ^pass caseError!

Item was changed:
+ ----- Method: GIFReadWriter>>writeBitData: (in category 'private - encoding') -----
+ writeBitData: bits
- ----- Method: GIFReadWriter>>writeBitData: (in category 'private-encoding') -----
- writeBitData: bits
         "using modified Lempel-Ziv Welch algorithm."
+        | encoder initCodeSize |
+        encoder := LzwGifEncoder new
+                rowByteSize: (width * 8 + 31) // 32 * 4;
+                extent: width at height;
+                codeStream: stream.
+        initCodeSize := bitsPerPixel <= 1
+                ifTrue: [ 2 ]
+                ifFalse: [ bitsPerPixel ].
+        encoder minimumCodeSize: initCodeSize.
+        encoder encode: bits.!
-
-        | maxBits maxMaxCode tSize initCodeSize ent tShift fCode pixel index disp nomatch |
-        pass := 0.
-        xpos := 0.
-        ypos := 0.
-        rowByteSize := width * 8 + 31 // 32 * 4.
-        remainBitCount := 0.
-        bufByte := 0.
-        bufStream := WriteStream on: (ByteArray new: 256).
-
-        maxBits := 12.
-        maxMaxCode := 1 bitShift: maxBits.
-        tSize := 5003.
-        prefixTable := Array new: tSize.
-        suffixTable := Array new: tSize.
-
-        initCodeSize := bitsPerPixel <= 1 ifTrue: [2] ifFalse: [bitsPerPixel].
-        self nextPut: initCodeSize.
-        self setParameters: initCodeSize.
-
-        tShift := 0.
-        fCode := tSize.
-        [fCode < 65536] whileTrue:
-                [tShift := tShift + 1.
-                fCode := fCode * 2].
-        tShift := 8 - tShift.
-        1 to: tSize do: [:i | suffixTable at: i put: -1].
-
-        self writeCodeAndCheckCodeSize: clearCode.
-        ent := self readPixelFrom: bits.
-        [(pixel := self readPixelFrom: bits) == nil] whileFalse:
-                [
-                fCode := (pixel bitShift: maxBits) + ent.
-                index := ((pixel bitShift: tShift) bitXor: ent) + 1.
-                (suffixTable at: index) = fCode
-                        ifTrue: [ent := prefixTable at: index]
-                        ifFalse:
-                                [nomatch := true.
-                                (suffixTable at: index) >= 0
-                                        ifTrue:
-                                                [disp := tSize - index + 1.
-                                                index = 1 ifTrue: [disp := 1].
-                                                "probe"
-                                                [(index := index - disp) < 1 ifTrue: [index := index + tSize].
-                                                (suffixTable at: index) = fCode
-                                                        ifTrue:
-                                                                [ent := prefixTable at: index.
-                                                                nomatch := false.
-                                                                "continue whileFalse:"].
-                                                nomatch and: [(suffixTable at: index) > 0]]
-                                                        whileTrue: ["probe"]].
-                                "nomatch"
-                                nomatch ifTrue:
-                                        [self writeCodeAndCheckCodeSize: ent.
-                                        ent := pixel.
-                                        freeCode < maxMaxCode
-                                                ifTrue:
-                                                        [prefixTable at: index put: freeCode.
-                                                        suffixTable at: index put: fCode.
-                                                        freeCode := freeCode + 1]
-                                                ifFalse:
-                                                        [self writeCodeAndCheckCodeSize: clearCode.
-                                                        1 to: tSize do: [:i | suffixTable at: i put: -1].
-                                                        self setParameters: initCodeSize]]]].
-        prefixTable := suffixTable := nil.
-        self writeCodeAndCheckCodeSize: ent.
-        self writeCodeAndCheckCodeSize: eoiCode.
-        self flushCode.
-
-        self nextPut: 0.        "zero-length packet"
- !

Item was removed:
- ----- Method: GIFReadWriter>>writeCode: (in category 'private-encoding') -----
- writeCode: aCode
-        self nextBitsPut: aCode!

Item was removed:
- ----- Method: GIFReadWriter>>writeCodeAndCheckCodeSize: (in category 'private-encoding') -----
- writeCodeAndCheckCodeSize: aCode
-        self writeCode: aCode.
-        self checkCodeSize!

Item was added:
+ ----- Method: GIFReadWriter>>writeDisposal:toPackedByte: (in category 'writing') -----
+ writeDisposal: aSymbol toPackedByte: aByte
+        "Using the GIF Graphics Control Extension
+        packed byte format, respond with a modified version
+        of the passed byte that includes the correct 3-bit
+        disposal code corresponding to the passed in symbol"
+
+        aSymbol = #restoreBackground
+                ifTrue: [
+                        "This is a value of 2 in the 3-bit structure,
+                        so 010, then shifted two to the left (equal to 8)"
+                        ^ aByte + (2 bitShift: 2) ].
+
+        aSymbol = #leaveCurrent
+                ifTrue: [
+                        "This is a value of 1 in the 3-bit structure,
+                        so 001, then shifted two to the left (equal to 4)"
+                        ^ aByte + (1 bitShift: 2) ].
+
+        aSymbol = #restorePrevState
+                ifTrue: [
+                        "This is a value of 3 in the 3-bit structure,
+                        so 011, then shifted two to the left (equal to 12)"
+                        ^ aByte + (3 bitShift: 2) ].
+        ^ aByte
+                !

Item was added:
+ ----- Method: GIFReadWriter>>writeFrameHeader: (in category 'accessing') -----
+ writeFrameHeader: anImageFrame
+        "Write any Extensions and/or headers that apply
+        to individual frames/subimages"
+        | interlaceByte |
+        anImageFrame delay notNil | transparentIndex notNil ifTrue: [
+                self writeGCEForFrame: anImageFrame ].
+
+        "Next is the image descriptor"
+        self
+                nextPut: ImageSeparator;
+                writeWord: (anImageFrame offset x);
+                writeWord: (anImageFrame offset y);
+                writeWord: (anImageFrame form extent x);
+                writeWord: (anImageFrame form extent y).
+
+        interlaceByte := interlace
+                ifTrue: [ 64 ]
+                ifFalse: [ 0 ].
+        self nextPut: interlaceByte
+        !

Item was added:
+ ----- Method: GIFReadWriter>>writeGCEForFrame: (in category 'private - encoding') -----
+ writeGCEForFrame: anAnimatedImageFrame
+        "Writes a Graphics Control Extension onto
+        the output stream for the given image frame"
+        | nextDelay packedByte |
+        nextDelay := anAnimatedImageFrame delay.
+        anAnimatedImageFrame delay ifNil: [ nextDelay := 0 ].
+        "Set the bits of the packed byte"
+        "====
+                Reserved                      3 Bits (Ignore)
+                Disposal Method               3 Bits
+                User Input Flag               1 Bit  (Ignore)
+                Transparent Color Flag        1 Bit
+                ==="
+        packedByte := 0.
+        transparentIndex
+                ifNotNil: [ packedByte := 1 ].
+        packedByte := self
+                writeDisposal: (anAnimatedImageFrame disposal)
+                toPackedByte: packedByte.
+
+        self
+                nextPut: Extension;
+                nextPutAll: #(249 4) asByteArray;
+                nextPut: packedByte;
+                "nextPut: (transparentIndex
+                                ifNil: [ 0 ]
+                                ifNotNil: [ 9 ]);"
+                writeWord: nextDelay // 10;
+                nextPut: (transparentIndex ifNil: [ 0 ]);
+                nextPut: 0.!

Item was changed:
+ ----- Method: GIFReadWriter>>writeHeader (in category 'private - encoding') -----
- ----- Method: GIFReadWriter>>writeHeader (in category 'private-encoding') -----
  writeHeader
-
         | byte |
+        "Write the overall image file header onto the
+        output stream. This includes the global information
+        about the file, such as canvasWidth etc. Only do so
+        if the stream is in the initial position."
+        stream position = 0 ifFalse: [ ^ self ].
-        stream position = 0 ifTrue: [
-                "For first image only"
-                self
-                        nextPutAll: #[71 73 70 56 57 97]; "'GIF89a' asByteArray"
-                        writeWord: width; "Screen Width"
-                        writeWord: height.      "Screen Height"
-                byte := 16r80.  "has color map"
-                byte := byte bitOr: ((bitsPerPixel - 1) bitShift: 5).  "color resolution"
-                byte := byte bitOr: bitsPerPixel - 1.  "bits per pixel"
-                self
-                        nextPut: byte;
-                        nextPut: 0;             "background color."
-                        nextPut: 0.             "reserved"
-                colorPalette do: [:pixelValue |
-                        self    nextPut: ((pixelValue bitShift: -16) bitAnd: 255);
-                                nextPut: ((pixelValue bitShift: -8) bitAnd: 255);
-                                nextPut: (pixelValue bitAnd: 255)].
-                loopCount ifNotNil: [
-                        "Write a Netscape loop chunk"
-                        self
-                                nextPut: Extension;
-                                nextPutAll: #[255 11 78 69 84 83 67 65 80 69 50 46 48 3 1];
-                                writeWord: loopCount;
-                                nextPut: 0]].

+        self nextPutAll: 'GIF89a' asByteArray.
+        self writeWord: width.  "Screen Width"
+        self writeWord: height. "Screen Height"
+        byte := 128.    "has color map"
+        byte := byte bitOr: (bitsPerPixel - 1 bitShift: 5).     "color resolution"
+        byte := byte bitOr: bitsPerPixel - 1.   "bits per pixel"
-        (delay notNil and: [ transparentIndex notNil ]) ifTrue: [
-                self nextPut: Extension;
-                        nextPutAll: #[16rF9 4];
-                        nextPut: (transparentIndex ifNil: [0] ifNotNil: [9]);
-                        writeWord: (delay ifNil: [0] ifNotNil: [delay]);
-                        nextPut: (transparentIndex ifNil: [0] ifNotNil: [transparentIndex]);
-                        nextPut: 0].
-
-        self
-                nextPut: ImageSeparator;
-                writeWord: 0;           "Image Left"
-                writeWord: 0;           "Image Top"
-                writeWord: width;       "Image Width"
-                writeWord: height.      "Image Height"
-        byte := interlace ifTrue: [16r40] ifFalse: [0].
         self nextPut: byte.
+        self nextPut: 0.        "background color."
+        self nextPut: 0.        "reserved / unused 'pixel aspect ratio"
+        colorPalette do:
+                [ :pixelValue |
+                self
+                        nextPut: ((pixelValue bitShift: -16) bitAnd: 255);
+                        nextPut: ((pixelValue bitShift: -8) bitAnd: 255);
+                        nextPut: (pixelValue bitAnd: 255) ].
+        loopCount notNil ifTrue:
+                [ self writeNetscapeExtension ].!
- !

Item was added:
+ ----- Method: GIFReadWriter>>writeNetscapeExtension (in category 'private - encoding') -----
+ writeNetscapeExtension
+        "Writes a GIF Application Extension corresponding
+        to the NETSCAPE2.0 version, with specifies the loopCount."
+        self
+                nextPut: Extension;
+                nextPut: 255; "Indicates Application Extension"
+                nextPut: 11; "Indicates how many bytes follow, almost always 11"
+                nextPutAll: ('NETSCAPE2.0' asByteArray);
+                nextPut: 3;
+                nextPut: 1;
+                writeWord: (loopCount ifNil: [ 0 ]);
+                nextPut: 0.!

Item was changed:
+ ----- Method: GIFReadWriter>>writeWord: (in category 'private - encoding') -----
- ----- Method: GIFReadWriter>>writeWord: (in category 'private-encoding') -----
  writeWord: aWord
         self nextPut: (aWord bitAnd: 255).
         self nextPut: ((aWord bitShift: -8) bitAnd: 255).
         ^aWord!

Item was added:
+ Object subclass: #LzwGifDecoder
+        instanceVariableNames: 'suffixTable prefixTable eoiCode clearCode bitMask codeSize minimumCodeSize maxCode nextAvailableCode numLeftoverBits codeStream codeStreamBuffer outBlock'
+        classVariableNames: ''
+        poolDictionaries: ''
+        category: 'Graphics-Files'!
+
+ !LzwGifDecoder commentStamp: '' prior: 0!
+ I implement the modified Lempel-Ziv-Welch (LZW) algorithm for lossless GIF decompression. My primary purpose is to decode streams of bytes that have been encoded with this modified version of LZW as used in the GIF standard.
+
+ My instances require, at minimum, a maximum code size (via #maxCode:), a minimum code size (via #minimumCodeSize:), and of course a stream of bytes to decode (via #codeStream:). Once these are set, implementors can simply send the #decode message, which will respond with a decoded ByteArray.
+
+ Optionally, implementors can make use of the #onDecodedBit: message, which takes a Block with a single argument corresponding to a decoded bit. This Block is called each time a complete byte/bit-level value for the bitmap has been decoded.
+
+ For an example of use, see GIFReadWriter >> #readBitDataOnFrame:.
+
+ I am separated out from GIFReadWriter for clarity and better debugging.
+
+
+
+ See:
+ https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch
+ https://www.w3.org/Graphics/GIF/spec-gif89a.txt
+
+ !

Item was added:
+ ----- Method: LzwGifDecoder>>bufferData (in category 'as yet unclassified') -----
+ bufferData
+        "The GIF Format stores the data for each image in sub-blocks of up to 255 bytes.
+        Decoding LZW for such small chunks adds a LOT of overhead.
+        So instead, we buffer *all* of the data into one ByteArray and provide stream access
+        to it.
+        Data sub-blocks are structured such that the first byte gives the size of the data chunk
+        that follows. A size of 0 indicates that there is no more data to deal with"
+        | chunkSize buff |
+        buff := WriteStream on: ByteArray new.
+        chunkSize := codeStream next.
+        [ chunkSize > 0 ] whileTrue: [
+                buff nextPutAll: (codeStream next: chunkSize).
+                chunkSize := codeStream next ].
+        "chunkSize > 0 ifTrue: [ buff nextPutAll: (codeStream next: chunkSize)]."
+        codeStreamBuffer := buff contents readStream!

Item was added:
+ ----- Method: LzwGifDecoder>>checkCodeSize (in category 'private') -----
+ checkCodeSize
+        "Ensure that the next available code to enter
+        into the table is not equal to the bitMask.
+        If it is, we increment the code size and update the
+        mask value."
+        nextAvailableCode := nextAvailableCode + 1.
+        (nextAvailableCode bitAnd: bitMask) = 0
+                ifTrue: [
+                        "GIF89a specifies a 'deferred' clearCode
+                        implementation, which means we keep going
+                        with the current table even if its full but
+                        a clear hasn't been found. We use the max
+                        code size at that point."
+                        nextAvailableCode >= maxCode ifTrue: [ ^ self ].
+                        codeSize := codeSize + 1.
+                        bitMask := bitMask + nextAvailableCode ].!

Item was added:
+ ----- Method: LzwGifDecoder>>codeStream: (in category 'private') -----
+ codeStream: aReadableStream
+        "Set the stream of encoded bytes we will decode
+        to be the internal codeStream. We read off the first
+        byte immediately, which tells us how many subsequent bytes
+        to use in our buffer for decoding"
+        | chunkSize buff |
+        codeStream := aReadableStream.
+        "chunkSize := codeStream next.
+        codeStreamBuffer := ReadStream on: (codeStream next: chunkSize)."
+        self bufferData
+        !

Item was added:
+ ----- Method: LzwGifDecoder>>decode (in category 'api') -----
+ decode
+        | currentCode prevCode outStream |
+        self initializeTables.
+        outStream := WriteStream with: (ByteArray new).
+        numLeftoverBits := 0.
+        currentCode := self nextCode.
+        currentCode = clearCode ifFalse: [ ^ self error: 'First code on the stream should always be the clear code!!' ].
+
+        "The first real code simply gets output
+        onto the stream, then we enter the loop"
+        currentCode := self nextCode.
+        self writeBit: currentCode on: outStream.
+        prevCode := currentCode.
+        currentCode := self nextCode.
+        [ currentCode = eoiCode ] whileFalse: [
+                currentCode = clearCode
+                        ifTrue: [
+                                self initializeTables.
+                                currentCode := self nextCode.
+                                self
+                                        writeBit: (suffixTable at: (currentCode + 1))
+                                        on: outStream.
+                                prevCode := nil ]
+                        ifFalse: [ self handleCode: currentCode withPreviousCode: prevCode on: outStream ].
+                prevCode := currentCode.
+                currentCode := self nextCode ].
+        ^ outStream contents.
+
+
+        !

Item was added:
+ ----- Method: LzwGifDecoder>>handleCode:withPreviousCode:on: (in category 'private') -----
+ handleCode: anInteger withPreviousCode: prevInteger on: aWriteStream
+        "Check for the code in the tables
+        and perform the appropriate LZW action"
+        | first searchIndex searchStack |
+        "The code already exists in the table"
+        anInteger < nextAvailableCode
+                ifTrue: [
+                        anInteger < clearCode
+                                "If it's less than the clearCode
+                                then it is one of the original entries
+                                and we can simply use the suffix"
+                                ifTrue: [
+                                        first := (suffixTable at: (anInteger + 1)).
+                                        self writeBit: first on: aWriteStream ]
+                                "Otherwise we need to loop backwards along
+                                the prefix index values and take the suffix each
+                                time"
+                                ifFalse: [
+                                        searchStack := OrderedCollection new.
+                                        searchIndex := anInteger + 1.
+                                        [ searchIndex > clearCode ] whileTrue: [
+                                                searchStack add: (suffixTable at: searchIndex).
+                                                searchIndex := (prefixTable at: searchIndex) + 1 ].
+                                        searchStack add: (suffixTable at: searchIndex).
+                                        first := searchStack last.
+                                        searchStack reverseDo: [ :int |
+                                                self writeBit: int on: aWriteStream ] ].
+                        ]
+                ifFalse: [
+                        "Here, the incoming code is not yet in the code tables"
+                        prevInteger < clearCode
+                                ifTrue: [
+                                        first := (suffixTable at: (prevInteger + 1)).
+                                        self
+                                                writeBit: first on: aWriteStream;
+                                                writeBit: first on: aWriteStream.
+                                         ]
+                                ifFalse: [
+                                        searchStack := OrderedCollection new.
+                                        searchIndex := prevInteger + 1.
+                                        [ searchIndex > clearCode ] whileTrue: [
+                                                searchStack add: (suffixTable at: searchIndex).
+                                                searchIndex := (prefixTable at: searchIndex) + 1 ].
+                                        searchStack add: (suffixTable at: searchIndex).
+                                        first := searchStack last.
+                                        searchStack reverseDo: [ :int |
+                                                self writeBit: int on: aWriteStream ].
+                                        self writeBit: first on: aWriteStream ].
+                        ].
+                "We add prevCode and the new
+                suffix to a new entry in the code table, but
+                only if we aren't at the max. NOTE: due to
+                GIF 89a spec's 'deferred clear', if you get to
+                the maxCode and haven't seen a clear, you stop
+                writing to the tables but continue querying."
+                nextAvailableCode >= maxCode
+                        ifFalse: [
+                                suffixTable at: (nextAvailableCode + 1) put: first.
+                                prefixTable at: (nextAvailableCode + 1) put: prevInteger ].
+                self checkCodeSize.!

Item was added:
+ ----- Method: LzwGifDecoder>>initializeTables (in category 'initialization') -----
+ initializeTables
+        "The two arrays are our lookup tables.
+        We do this instead of Dictionaries because
+        the method is much, much faster"
+        prefixTable := Array new: (maxCode).
+        suffixTable := Array new: (maxCode).
+
+        "The initial code size and mask settings
+        also get reinitialized each time"
+        codeSize := minimumCodeSize + 1.
+        clearCode := (1 bitShift: minimumCodeSize).
+        eoiCode := clearCode + 1.
+        nextAvailableCode := clearCode + 2.
+        bitMask := (1 bitShift: codeSize) - 1.
+
+        "Fill the tables with the initial values"
+        1 to: clearCode do: [ :n |
+                prefixTable at: n put: (n - 1).
+                suffixTable at: n put: (n - 1) ].!

Item was added:
+ ----- Method: LzwGifDecoder>>maxCode: (in category 'accessing') -----
+ maxCode: anInteger
+        maxCode := anInteger!

Item was added:
+ ----- Method: LzwGifDecoder>>minimumCodeSize: (in category 'accessing') -----
+ minimumCodeSize: anInteger
+        minimumCodeSize := anInteger!

Item was added:
+ ----- Method: LzwGifDecoder>>nextByte (in category 'private - packing') -----
+ nextByte
+        ^ codeStreamBuffer next!

Item was added:
+ ----- Method: LzwGifDecoder>>nextCode (in category 'private') -----
+ nextCode
+        | integer numBitsRead newRemainder shiftAmount byte |
+        "Retrieve the next code of codeSize bits.
+        Store the remaining bits etc for later computation"
+        integer := 0.
+        numLeftoverBits = 0
+                ifTrue: [
+                        numBitsRead := 8.
+                        shiftAmount := 0 ]
+                ifFalse: [
+                        numBitsRead := numLeftoverBits.
+                        shiftAmount := numLeftoverBits - 8 ].
+                [ numBitsRead < codeSize ] whileTrue: [
+                        byte := self nextByte.
+                        byte == nil ifTrue: [ ^ eoiCode ].
+                        integer := integer + (byte bitShift: shiftAmount).
+                        shiftAmount := shiftAmount + 8.
+                        numBitsRead := numBitsRead + 8 ].
+                (newRemainder := numBitsRead - codeSize) = 0
+                        ifTrue: [ byte := self nextByte ]
+                        ifFalse: [ byte := self peekByte ].
+                byte == nil ifTrue: [ ^ eoiCode ].
+                numLeftoverBits := newRemainder.
+                ^ integer + (byte bitShift: shiftAmount) bitAnd: bitMask.!

Item was added:
+ ----- Method: LzwGifDecoder>>onDecodedBit: (in category 'accessing') -----
+ onDecodedBit: aBlock
+        "This block will be executed once each time a new
+        value is decoded from the stream, with the value
+        as the sole argument passed to the block"
+        outBlock := aBlock!

Item was added:
+ ----- Method: LzwGifDecoder>>peekByte (in category 'private - packing') -----
+ peekByte
+        ^ codeStreamBuffer peek!

Item was added:
+ ----- Method: LzwGifDecoder>>writeBit:on: (in category 'writing') -----
+ writeBit: anInteger on: aWriteStream
+        "Write the incoming decoded value onto a
+        writestream. If I have an outBlock set,
+        send this value also"
+        aWriteStream nextPut: anInteger.
+        outBlock ifNil: [ ^ self ].
+        outBlock value: anInteger.!

Item was added:
+ Object subclass: #LzwGifEncoder
+        instanceVariableNames: 'suffixTable prefixTable eoiCode clearCode codeSize minimumCodeSize maxCode nextAvailableCode numLeftoverBits bitBuffer codeStream codeStreamBuffer rowByteSize xPos yPos dimensions'
+        classVariableNames: ''
+        poolDictionaries: ''
+        category: 'Graphics-Files'!
+
+ !LzwGifEncoder commentStamp: '' prior: 0!
+ I implement the modified Lempel-Ziv-Welch (LZW) algorithm for lossless GIF bitmap compression. My primary purpose is to encode/compress streams of bitmap bytes as specified by the GIF standard.
+
+ My instances require at minimum:
+ - A size of bytes in a row of bitmap bits for the image (#rowByteSize:)
+ - The extent of the image being encoded (#extent:)
+ - An array of bits in a bitmap (as bytes) for encoding (sent with #encode:)
+ - A stream of Bytes on which to output the encoded bytes (#codeStream:)
+ - A minimum code size as specified from GIF header information (#minimimCodeSize:)
+
+ Once all of these are set, implementors simply send the #encode: message along with a
+ collection of bitmap values as bytes that need to be encoded. Instead of responding with a collection of encoded bytes, #encode: will write to the output stream specified by #codeStream: directly.
+
+ For an example of use, see GIFReadWriter >> #writeBitData:
+
+ NOTE: LZW compression for GIFs is complex and the #encode: method is largely taken verbatim from Kazuki Yasumatsu's 1995 Squeak implementation (as opposed to the Decoder, which was heavily refactored for readability and comprehension). Any contributions to fleshing this out in a comprehensible way are much appreciated!!
+
+ See:
+ https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch
+ https://www.w3.org/Graphics/GIF/spec-gif89a.txt!

Item was added:
+ ----- Method: LzwGifEncoder>>checkCodeSize (in category 'private') -----
+ checkCodeSize
+        "Determine whether or not we need to increment
+        the codeSize"
+        (nextAvailableCode > maxCode and: [ codeSize < 12 ])
+                ifTrue: [
+                        codeSize := codeSize + 1.
+                        maxCode := (1 bitShift: codeSize) - 1 ].!

Item was added:
+ ----- Method: LzwGifEncoder>>checkSettings (in category 'private') -----
+ checkSettings
+        "Ensure that the appropriate variables
+        that are needed for proper encoding
+        have been set"
+        codeStream ifNil: [ ^ self error: 'You must set a codeStream (byte stream) to write onto!!' ].
+        dimensions ifNil: [ ^ self error: 'You must provide the extent of the image we will encode!!' ].
+        rowByteSize ifNil: [ ^ self error: 'You must provide a rowByteSize for the supplied image bits!!' ].!

Item was added:
+ ----- Method: LzwGifEncoder>>codeStream: (in category 'accessing') -----
+ codeStream: aByteStream
+        codeStream := aByteStream.!

Item was added:
+ ----- Method: LzwGifEncoder>>dimensions: (in category 'accessing') -----
+ dimensions: anExtentPoint
+        "Set the extent (as point) of the
+        image that will be encoded"
+        dimensions := anExtentPoint!

Item was added:
+ ----- Method: LzwGifEncoder>>encode: (in category 'converting') -----
+ encode: bits
+        | maxBits maxMaxCode tSize tShift fCode ent pixel index nomatch disp |
+        self checkSettings.
+        xPos := yPos := 0.
+        codeStream nextPut: minimumCodeSize.
+        bitBuffer := 0.
+        numLeftoverBits := 0.
+        codeStreamBuffer := WriteStream on: (ByteArray new: 256).
+        self initializeParameters.
+
+        "These temp vars are taken from the
+        original GIFReadWriter implementation"
+        maxBits := 12.
+        maxMaxCode := 1 bitShift: maxBits.
+        tSize := 5003.
+        prefixTable := Array new: tSize.
+        suffixTable := Array new: tSize.
+        tShift := 0.
+        fCode := tSize.
+        [ fCode < 65536 ] whileTrue: [
+                tShift := tShift + 1.
+                fCode := fCode * 2 ].
+        tShift := 8 - tShift.
+        1 to: tSize do: [ :i |
+                suffixTable at: i put: -1 ].
+
+        "We immediately write the clearCode
+        to the output stream"
+        self writeCodeAndCheckCodeSize: clearCode.
+
+        "This loop is also taken from the original
+        GIFReadWriter implementation"
+        ent := self readPixelFrom: bits.
+        [ (pixel := self readPixelFrom: bits) == nil ] whileFalse:
+                [ fCode := (pixel bitShift: maxBits) + ent.
+                index := ((pixel bitShift: tShift) bitXor: ent) + 1.
+                (suffixTable at: index) = fCode
+                        ifTrue: [ ent := prefixTable at: index ]
+                        ifFalse:
+                                [ nomatch := true.
+                                (suffixTable at: index) >= 0 ifTrue:
+                                        [ disp := tSize - index + 1.
+                                        index = 1 ifTrue: [ disp := 1 ].
+                                        "probe"
+
+                                        [ (index := index - disp) < 1 ifTrue: [ index := index + tSize ].
+                                        (suffixTable at: index) = fCode ifTrue:
+                                                [ ent := prefixTable at: index.
+                                                nomatch := false
+                                                "continue whileFalse:" ].
+                                        nomatch and: [ (suffixTable at: index) > 0 ] ] whileTrue:
+                                                [ "probe"
+                                                 ] ].
+                                "nomatch"
+                                nomatch ifTrue:
+                                        [ self writeCodeAndCheckCodeSize: ent.
+                                        ent := pixel.
+                                        nextAvailableCode < maxMaxCode
+                                                ifTrue:
+                                                        [ prefixTable
+                                                                at: index
+                                                                put: nextAvailableCode.
+                                                        suffixTable
+                                                                at: index
+                                                                put: fCode.
+                                                        nextAvailableCode := nextAvailableCode + 1 ]
+                                                ifFalse:
+                                                        [ self writeCodeAndCheckCodeSize: clearCode.
+                                                        1
+                                                                to: tSize
+                                                                do:
+                                                                        [ :i |
+                                                                        suffixTable
+                                                                                at: i
+                                                                                put: -1 ].
+                                                        self initializeParameters ] ] ] ].
+        prefixTable := suffixTable := nil.
+        self writeCodeAndCheckCodeSize: ent.
+        self writeCodeAndCheckCodeSize: eoiCode.
+        self flushBits.
+        codeStream nextPut: 0.
+                                !

Item was added:
+ ----- Method: LzwGifEncoder>>extent: (in category 'accessing') -----
+ extent: anExtentPoint
+        "Set the extent (as point) of the
+        image that will be encoded"
+        dimensions := anExtentPoint!

Item was added:
+ ----- Method: LzwGifEncoder>>flushBits (in category 'private - bits access') -----
+ flushBits
+        numLeftoverBits = 0 ifFalse:
+                [ self nextBytePut: bitBuffer.
+                numLeftoverBits := 0 ].
+        self flushBuffer!

Item was added:
+ ----- Method: LzwGifEncoder>>flushBuffer (in category 'private') -----
+ flushBuffer
+        "Write out the current codeStreamBuffer size,
+        followed by its actual contents, to the true
+        output codeStream"
+        codeStreamBuffer isEmpty ifTrue: [ ^ self ].
+        codeStream
+                nextPut: codeStreamBuffer size;
+                nextPutAll: codeStreamBuffer contents.
+        codeStreamBuffer := (ByteArray new: 256) writeStream.!

Item was added:
+ ----- Method: LzwGifEncoder>>initializeParameters (in category 'initialization') -----
+ initializeParameters
+        "The initial code size and mask settings
+        also get reinitialized each time"
+        codeSize := minimumCodeSize + 1.
+        clearCode := (1 bitShift: minimumCodeSize).
+        eoiCode := clearCode + 1.
+        nextAvailableCode := clearCode + 2.
+        maxCode := (1 bitShift: codeSize) - 1.!

Item was added:
+ ----- Method: LzwGifEncoder>>minimumCodeSize: (in category 'accessing') -----
+ minimumCodeSize: anInteger
+        minimumCodeSize := anInteger!

Item was added:
+ ----- Method: LzwGifEncoder>>nextBytePut: (in category 'private - packing') -----
+ nextBytePut: anInteger
+        "Write a complete byte to the output byteStream.
+        Be sure to reset one we reach the limit, which is
+        255 for GIF files. Then write the length of the next
+        byte chunks to the stream also"
+        codeStreamBuffer nextPut: anInteger.
+        codeStreamBuffer size >= 254
+                ifTrue: [ self flushBuffer ].!

Item was added:
+ ----- Method: LzwGifEncoder>>nextCodePut: (in category 'private - bits access') -----
+ nextCodePut: anInteger
+        "Attempt to put the bits on the
+        output stream. If we have remaining bits,
+        then we need to use bitwise operations to
+        fill the next byte properly before putting
+        it on the output stream"
+        | numBitsWritten shiftCount newInteger |
+        shiftCount := 0.
+        numLeftoverBits = 0
+                ifTrue: [
+                        numBitsWritten := 8.
+                        newInteger := anInteger ]
+                ifFalse: [
+                        numBitsWritten := numLeftoverBits.
+                        newInteger := bitBuffer + (anInteger bitShift: 8 - numLeftoverBits) ].
+        [ numBitsWritten < codeSize ] whileTrue: [
+                self nextBytePut: ((newInteger bitShift: shiftCount) bitAnd: 255).
+                shiftCount := shiftCount - 8.
+                numBitsWritten := numBitsWritten + 8 ].
+        (numLeftoverBits := numBitsWritten - codeSize) = 0
+                ifTrue: [ self nextBytePut: (newInteger bitShift: shiftCount) ]
+                ifFalse: [ bitBuffer := newInteger bitShift: shiftCount ].
+        ^ anInteger
+        !

Item was added:
+ ----- Method: LzwGifEncoder>>readPixelFrom: (in category 'private - encoding') -----
+ readPixelFrom: bits
+        "Using the current x and y positions and
+        the specified byte size for a row, determine
+        the value for the next pixel in the provided bits"
+        | pixel |
+        yPos >= (dimensions y) ifTrue: [ ^ nil ].
+        pixel := bits byteAt: yPos * rowByteSize + xPos + 1.
+        self updatePixelPosition.
+        ^ pixel
+        !

Item was added:
+ ----- Method: LzwGifEncoder>>rowByteSize: (in category 'accessing') -----
+ rowByteSize: anInteger
+        rowByteSize := anInteger!

Item was added:
+ ----- Method: LzwGifEncoder>>updatePixelPosition (in category 'private') -----
+ updatePixelPosition
+        "Increment the xPos. If we are at the width
+        position, set xPos to 0 and increment the yPos"
+        xPos := xPos + 1.
+        xPos >= (dimensions x) ifFalse: [ ^ self ].
+        xPos := 0.
+        yPos := yPos + 1.!

Item was added:
+ ----- Method: LzwGifEncoder>>writeCodeAndCheckCodeSize: (in category 'writing') -----
+ writeCodeAndCheckCodeSize: aCode
+        self nextCodePut: aCode.
+        self checkCodeSize.!


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20220504/9494daed/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: senders.gif
Type: image/gif
Size: 573233 bytes
Desc: senders.gif
URL: <http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20220504/9494daed/attachment-0002.gif>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: viewtransitions-interruptedbad.gif
Type: image/gif
Size: 30112 bytes
Desc: viewtransitions-interruptedbad.gif
URL: <http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20220504/9494daed/attachment-0003.gif>


More information about the Squeak-dev mailing list