Author Topic: SaveGIF  (Read 4091 times)

0 Members and 1 Guest are viewing this topic.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
SaveGIF
« on: August 14, 2019, 06:18:46 am »
Code: QB64: [Select]
  1. '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%GIF STUFF%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  2. DIM SHARED MakeGif_OutBuffer AS STRING
  3. DIM SHARED MakeGif_OStartAddress AS INTEGER, MakeGif_OAddress AS INTEGER
  4. DIM SHARED MakeGif_OEndAddress AS INTEGER, MakeGif_OSeg AS INTEGER
  5. DIM SHARED MakeGif_CodeSize AS INTEGER, MakeGif_CurrnetBit AS INTEGER, MakeGif_Char AS LONG
  6. DIM SHARED MakeGIF_BlockLength AS INTEGER, MakeGif_X AS INTEGER, MakeGif_Y AS INTEGER
  7. DIM SHARED MakeGif_MinX AS INTEGER, MakeGif_MinY AS INTEGER
  8. DIM SHARED MakeGif_MaxX AS INTEGER, MakeGif_MaxY AS INTEGER
  9. DIM SHARED MakeGif_Done AS INTEGER, MakeGif_GIFfile AS INTEGER, MakeGif_LastLoc AS LONG
  10. '%%%%%%%%%%%%%%%%%%%%%%%%%%%END OF GIF STUFF%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  11.  
  12.  
  13.  
  14.  
  15. DIM Demo_T
  16. SCREEN _NEWIMAGE(640, 480, 256)
  17. FOR i = 1 TO 100
  18.     LINE (RND * 640, RND * 480)-(RND * 640, RND * 480), i, BF 'draw some junk on the screen
  19. SaveGIF "booga.gif", 0, 0, 0, 639, 479
  20. Demo_T = _LOADIMAGE("booga.gif")
  21. SCREEN Demo_T
  22.  
  23.  
  24. '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%GIF STUFF%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  25. '-----------------------------------------------------------------------
  26. ' PDS 7.1 & QB4.5 GIF Compression Routine v1.00 By Rich Geldreich 1992
  27. '      Bug fixed and Overhauled for QB64 by Steve McNeill 2019
  28. '-----------------------------------------------------------------------
  29. SUB SaveGIF (file$, image AS LONG, Xstart, YStart, Xend, Yend)
  30.     CONST Table.Size = 7177 'hash table's size - must be a prime number!
  31.     'Variables all DIMMED so as to avoid any OPTION _EXPLICIT errors.
  32.     DIM Prefix(Table.Size - 1) AS INTEGER
  33.     DIM Suffix(Table.Size - 1) AS INTEGER
  34.     DIM code(Table.Size - 1) AS INTEGER
  35.     DIM ScreenX AS INTEGER
  36.     DIM ScreenY AS INTEGER
  37.     DIM B AS STRING
  38.     DIM NumColors AS INTEGER
  39.     DIM BitsPixel AS INTEGER
  40.     DIM StartSize AS INTEGER
  41.     DIM StartCode AS INTEGER
  42.     DIM StartMax AS INTEGER
  43.     DIM ColorBits AS INTEGER
  44.     DIM a1 AS INTEGER
  45.     DIM a AS STRING
  46.     DIM R AS INTEGER
  47.     DIM G AS INTEGER
  48.     DIM B1 AS INTEGER
  49.     DIM ImageWidth AS INTEGER
  50.     DIM ImageHeight AS INTEGER
  51.     DIM MaxCode AS INTEGER
  52.     DIM ClearCode AS INTEGER
  53.     DIM EOFCode AS INTEGER
  54.     DIM NextCode AS INTEGER
  55.     DIM a2 AS LONG
  56.     DIM Prefix AS INTEGER
  57.     DIM Suffix AS INTEGER
  58.     DIM Found AS INTEGER
  59.     DIM index AS INTEGER
  60.     DIM Offset AS INTEGER
  61.     DIM D AS INTEGER
  62.     DIM S AS INTEGER
  63.     D = _DEST: S = _SOURCE
  64.     _DEST image&: _SOURCE image&
  65.  
  66.     'MakeGif_MinX, MakeGif_MinY, MakeGif_MaxX, MakeGif_MaxY have the encoding window
  67.     ScreenX = _WIDTH: ScreenY = _HEIGHT
  68.     MakeGif_MinX = Xstart: MakeGif_MinY = YStart
  69.     MakeGif_MaxX = Xend: MakeGif_MaxY = Yend
  70.  
  71.     'Open GIF output file
  72.     MakeGif_GIFfile = FREEFILE 'use next free file
  73.     OPEN file$ FOR BINARY AS MakeGif_GIFfile
  74.     'Put GIF87a header at beginning of file
  75.     B$ = "GIF87a"
  76.     PUT MakeGif_GIFfile, , B$
  77.     'See how many colors are in this image...
  78.     NumColors = 256 'who cares about the old school graphic modes with fewer colors?  Not me!  Find a different encoder. :)
  79.     BitsPixel = 8 '8 bits per pixel
  80.     StartSize = 9 'first LZW code is 9 bits
  81.     StartCode = 256 'first free code
  82.     StartMax = 512 'maximum code in 9 bits
  83.     ColorBits = 6 'VGA
  84.  
  85.     PUT MakeGif_GIFfile, , ScreenX 'put screen's dimensions
  86.     PUT MakeGif_GIFfile, , ScreenY
  87.  
  88.     'pack colorbits and bits per pixel
  89.     a1 = 215 ' precalculated value: for 128 + (ColorBits - 1) * 16 + (BitsPixel - 1)
  90.     PUT MakeGif_GIFfile, , a1
  91.  
  92.     'throw a zero into the GIF file; reserved for future expansion of format (which will never come)
  93.     a$ = CHR$(0)
  94.     PUT MakeGif_GIFfile, , a$
  95.  
  96.     'Get the RGB palette from the screen and put it into the file...
  97.     FOR a1 = 0 TO 255
  98.         'Note: a BIOS call could be used here, but then we have to use
  99.         'the messy CALL INTERRUPT subs...
  100.         R = _RED(a1, image&)
  101.         G = _GREEN(a1, image&)
  102.         B1 = _BLUE(a1, image&)
  103.         a$ = CHR$(R): PUT MakeGif_GIFfile, , a$
  104.         a$ = CHR$(G): PUT MakeGif_GIFfile, , a$
  105.         a$ = CHR$(B1): PUT MakeGif_GIFfile, , a$
  106.     NEXT
  107.  
  108.  
  109.     'write out an image descriptor...
  110.     a$ = "," '"," is image seperator
  111.     PUT MakeGif_GIFfile, , a$ 'write it
  112.     PUT MakeGif_GIFfile, , MakeGif_MinX 'write out the image's location
  113.     PUT MakeGif_GIFfile, , MakeGif_MinY
  114.     ImageWidth = (MakeGif_MaxX - MakeGif_MinX + 1) 'find length & width of image
  115.     ImageHeight = (MakeGif_MaxY - MakeGif_MinY + 1)
  116.     PUT MakeGif_GIFfile, , ImageWidth 'store them into the file
  117.     PUT MakeGif_GIFfile, , ImageHeight
  118.     a$ = CHR$(BitsPixel - 1) '# bits per pixel in the image
  119.     PUT MakeGif_GIFfile, , a$
  120.  
  121.     a$ = CHR$(StartSize - 1) 'store the LZW minimum code size
  122.     PUT MakeGif_GIFfile, , a$
  123.  
  124.     'Initialize the vars needed by PutCode
  125.  
  126.     MakeGif_CurrnetBit = 0: MakeGif_Char = 0
  127.     MaxCode = StartMax 'the current maximum code size
  128.     MakeGif_CodeSize = StartSize 'the current code size
  129.     ClearCode = StartCode 'ClearCode & EOF code are the
  130.     EOFCode = StartCode + 1 ' first two entries
  131.     StartCode = StartCode + 2 'first free code that can be used
  132.     NextCode = StartCode 'the current code
  133.  
  134.     MakeGif_OutBuffer = STRING$(5000, 32) 'output buffer; for speedy disk writes
  135.     a2& = SADD(MakeGif_OutBuffer) 'find address of buffer
  136.     a2& = a2& - 65536 * (a2& < 0)
  137.     MakeGif_OSeg = VARSEG(MakeGif_OutBuffer) + (a2& \ 16) 'get segment + offset >> 4
  138.     MakeGif_OAddress = a2& AND 15 'get address into segment
  139.     MakeGif_OEndAddress = MakeGif_OAddress + 5000 'end of disk buffer
  140.     MakeGif_OStartAddress = MakeGif_OAddress 'current location in disk buffer
  141.     DEF SEG = MakeGif_OSeg
  142.  
  143.     FOR a1 = 0 TO Table.Size - 1 'clears the hashing table
  144.         Prefix(a1) = -1 '-1 = invalid entry
  145.         Suffix(a1) = -1
  146.         code(a1) = -1
  147.     NEXT
  148.  
  149.     PutCode ClearCode ' clear code
  150.  
  151.     MakeGif_X = Xstart: MakeGif_Y = YStart 'MakeGif_X & MakeGif_Y have the current pixel
  152.     Prefix = GetByte 'the first pixel is a special case
  153.     MakeGif_Done = 0 '-1 when image is complete
  154.  
  155.     DO 'while there are more pixels to encode
  156.         DO 'until we have a new string to put into the table
  157.             'get a pixel from the screen and see if we can find
  158.             'the new string in the table
  159.             Suffix = GetByte
  160.             GOSUB Hash 'is it there?
  161.             IF Found = -1 THEN Prefix = code(index) 'yup, replace the
  162.             'prefix:suffix string with whatever
  163.             'code represents it in the table
  164.         LOOP WHILE Found AND NOT MakeGif_Done 'don't stop unless we find a new string
  165.         PutCode Prefix 'output the prefix to the file
  166.         Prefix(index) = Prefix 'put the new string in the table
  167.         Suffix(index) = Suffix
  168.         code(index) = NextCode 'we've got to keep track if what code this is!
  169.         Prefix = Suffix 'Prefix=the last pixel pulled from the screen
  170.         NextCode = NextCode + 1 'get ready for the next code
  171.         IF NextCode = MaxCode + 1 THEN 'can an output code ever exceed
  172.             'the current code size?
  173.             'yup, increase the code size
  174.             MaxCode = MaxCode * 2
  175.             'Note: The GIF89a spec mentions something about a deferred clear
  176.             'code. When the clear code is deferred, codes are not entered
  177.             'into the hash table anymore. When the compression of the image
  178.             'starts to fall below a certain threshold, the clear code is
  179.             'sent and the hash table is cleared. The overall result is
  180.             'greater compression, because the table is cleared less often.
  181.             'This version of MakeGIF doesn't support this, because some GIF
  182.             'decoders crash when they attempt to enter too many codes
  183.             'into the string table.
  184.  
  185.             IF MakeGif_CodeSize = 12 THEN 'is the code size too big?
  186.                 PutCode ClearCode 'yup; clear the table and
  187.                 FOR a1 = 0 TO Table.Size - 1 'clears the hashing table
  188.                     Prefix(a1) = -1 '-1 = invalid entry
  189.                     Suffix(a1) = -1
  190.                     code(a1) = -1
  191.                 NEXT
  192.                 NextCode = StartCode
  193.                 MakeGif_CodeSize = StartSize
  194.                 MaxCode = StartMax
  195.             ELSE
  196.                 MakeGif_CodeSize = MakeGif_CodeSize + 1 'just increase the code size if
  197.             END IF 'it's not too high( not > 12)
  198.         END IF
  199.     LOOP UNTIL MakeGif_Done 'while we have more pixels
  200.     'Once MakeGif_Done, write out the last pixel, clear the disk buffer
  201.     'and fix up the last block so its count is correct
  202.     PutCode Prefix 'write last pixel
  203.     PutCode EOFCode 'send EOF code
  204.     IF MakeGif_CurrnetBit <> 0 THEN
  205.         PutCode 0 'flush out the last code...
  206.     END IF
  207.     PutByte 0
  208.     MakeGif_OutBuffer = LEFT$(MakeGif_OutBuffer, MakeGif_OAddress - MakeGif_OStartAddress)
  209.     PUT MakeGif_GIFfile, , MakeGif_OutBuffer
  210.     a$ = ";" + STRING$(8, &H1A) 'the 8 EOF chars is not standard,
  211.     'but many GIF's have them, so how
  212.     'much could it hurt?
  213.     PUT MakeGif_GIFfile, , a$
  214.     a$ = CHR$(255 - MakeGIF_BlockLength) 'correct the last block's count
  215.     PUT MakeGif_GIFfile, MakeGif_LastLoc, a$
  216.     CLOSE MakeGif_GIFfile
  217.     _DEST D: _SOURCE S 'restore the destination and source now that we're done.
  218.     EXIT SUB 'so we won't have any issues trying to run the hash routines below.
  219.  
  220.     'this is only one of a plethora of ways to search the table for
  221.     'a match! I used a binary tree first, but I switched to hashing
  222.     'cause it's quicker(perhaps the way I implemented the tree wasn't
  223.     'optimal... who knows!)
  224.  
  225.     Hash:
  226.     'hash the prefix & suffix(there are also many ways to do this...)
  227.     '?? is there a better formula?
  228.     index = ((Prefix * 256&) XOR Suffix) MOD Table.Size
  229.     '
  230.     '(Note: the table size(7177 in this case) must be a prime number, or
  231.     'else there's a chance that the routine will hang up... hate when
  232.     'that happens!)
  233.     '
  234.     'Calculate an offset just in case we don't find what we want on the
  235.     'first try...
  236.  
  237.     IF index = 0 THEN 'can't have Table.Size-0 !
  238.         Offset = 1
  239.     ELSE
  240.         Offset = Table.Size - index
  241.     END IF
  242.  
  243.     DO 'until we (1) find an empty entry or (2) find what we're lookin for
  244.         IF code(index) = -1 THEN 'is this entry blank?
  245.             Found = 0 'yup- we didn't find the string
  246.             RETURN
  247.             'is this entry the one we're looking for?
  248.         ELSEIF Prefix(index) = Prefix AND Suffix(index) = Suffix THEN
  249.             'yup, congrats you now understand hashing!!!
  250.             Found = -1
  251.             RETURN
  252.         ELSE
  253.             'shoot! we didn't find anything interesting, so we must
  254.             'retry- this is what slows hashing down. I could of used
  255.             'a bigger table, that would of speeded things up a little
  256.             'because this retrying would not happen as often...
  257.             index = index - Offset
  258.             IF index < 0 THEN 'too far down the table?
  259.                 'wrap back the index to the end of the table
  260.                 index = index + Table.Size
  261.             END IF
  262.         END IF
  263.     LOOP
  264.  
  265. 'Puts a byte into the GIF file & also takes care of each block.
  266. SUB PutByte (a) STATIC
  267.     MakeGIF_BlockLength = MakeGIF_BlockLength - 1 'are we at the end of a block?
  268.     IF MakeGIF_BlockLength <= 0 THEN ' yup,
  269.         MakeGIF_BlockLength = 255 'block length is now 255
  270.         MakeGif_LastLoc = LOC(MakeGif_GIFfile) + 1 + (MakeGif_OAddress - MakeGif_OStartAddress) 'remember the pos.
  271.         BufferWrite 255 'for later fixing
  272.     END IF
  273.     BufferWrite a 'put a byte into the buffer
  274.  
  275. 'Puts an LZW variable-bit code into the output file...
  276. SUB PutCode (a) STATIC
  277.     MakeGif_Char = MakeGif_Char + a * 2 ^ MakeGif_CurrnetBit 'put the char were it belongs;
  278.     MakeGif_CurrnetBit = MakeGif_CurrnetBit + MakeGif_CodeSize ' shifting it to its proper place
  279.     DO WHILE MakeGif_CurrnetBit > 7 'do we have a least one full byte?
  280.         PutByte MakeGif_Char AND 255 ' yup! mask it off and write it out
  281.         MakeGif_Char = MakeGif_Char \ 256 'shift the bit buffer right 8 bits
  282.         MakeGif_CurrnetBit = MakeGif_CurrnetBit - 8 'now we have 8 less bits
  283.     LOOP 'until we don't have a full byte
  284.  
  285.  
  286. SUB BufferWrite (a) STATIC
  287.     IF MakeGif_OAddress = MakeGif_OEndAddress THEN 'are we at the end of the buffer?
  288.         PUT MakeGif_GIFfile, , MakeGif_OutBuffer ' yup, write it out and
  289.         MakeGif_OAddress = MakeGif_OStartAddress ' start all over
  290.     END IF
  291.     POKE MakeGif_OAddress, a 'put byte in buffer
  292.     MakeGif_OAddress = MakeGif_OAddress + 1 'increment position
  293.  
  294. 'This routine gets one pixel from the display.
  295.     GetByte = POINT(MakeGif_X, MakeGif_Y) 'get the "byte"
  296.     MakeGif_X = MakeGif_X + 1 'increment MakeGif_X coordinate
  297.     IF MakeGif_X > MakeGif_MaxX THEN 'are we too far?
  298.         MakeGif_X = MakeGif_MinX 'go back to start
  299.         MakeGif_Y = MakeGif_Y + 1 'increment MakeGif_Y coordinate
  300.         IF MakeGif_Y > MakeGif_MaxY THEN 'are we too far down?
  301.             MakeGif_Done = -1 ' yup, flag it then
  302.         END IF
  303.     END IF
  304. '%%%%%%%%%%%%%%%%%%%%%%%%%%%END OF GIF STUFF%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  305.  

The wiki has an example of a GIF encoding routine, but it's... Meh!  (You can find it here: https://www.qb64.org/wiki/GIF_Creation )

As you can see from the wiki, it's based off the same code this is:   Routine v1.00 By Rich Geldreich 1992

Two problems I really have with the current version in the wiki:

1) A lot of the comments were stripped out and edited for some reason from the program, such as the wiki containing the single line:
        DO 'until we have a new string to put into the table

When, the original of Rich's had it written up as:
        DO 'until we have a new string to put into the table
            'get a pixel from the screen and see if we can find
            'the new string in the table


There's also a long comment which the wiki has as just:
   'Note: The GIF89a spec mentions something about a deferred clear code

Whereas, the original has this to say for us:
            'Note: The GIF89a spec mentions something about a deferred clear
            'code. When the clear code is deferred, codes are not entered
            'into the hash table anymore. When the compression of the image
            'starts to fall below a certain threshold, the clear code is
            'sent and the hash table is cleared. The overall result is
            'greater compression, because the table is cleared less often.
            'This version of MakeGIF doesn't support this, because some GIF
            'decoders crash when they attempt to enter too many codes
            'into the string table.

Needless to say, I personally am glad I stumbled across an old version of Rich's code on my hard drives, as I'm definitely finding the unedited comments a lot better to help me understand what's going on where, and why.

2) The second problem I have with the wiki version is the fact that it requires a DEFINT, without bothering to specify that (see it hidden away in the "main program"?), which led me to countless hours trying to sort out WHY the wiki version was working with the wiki example, and not with anything I actually tried to use it with...

3) (And I'll toss in a free, bonus problem #3 for you guys!)  Even after it got it up and going, it wasn't going very fast for me.  (Relatively speaking.)  I figured there must be a way to speed the routines up, so I dug around on the old drives, looking to see how I'd did such things before, in the past, and that's when I stumbled upon Rich's actual old code...


 
So, having the original to work with now, I started playing around with what would be needed to speed it up a bit and make it faster for us in QB64, while working to keep all the comments to help others understand what's going on inside the code.  I took out several of the old OUT statements and replaced them with a much simpler _RED, _GREEN, _BLUE version of things. I explicitedly dimmed all the variables so they wouldn't cause any issues with OPTION _EXPLICIT.  Fixed a glitch where it was checking the LOC(1) and not the LOC(GIFfile)....

A few tweaks here, a few minor changes there, a lot of DIM work.. and I now have the version posted above, which I thought I'd share for whomever might be interested in studying and learning the GIF format from. 
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: SaveGIF
« Reply #1 on: August 14, 2019, 06:24:19 am »
And the raw, unedited version of Rich's work is here:

Code: Text: [Select]
  1. DEFINT A-Z
  2.  
  3. DECLARE FUNCTION GetByte% ()
  4.  
  5. DECLARE SUB BufferWrite (a%)
  6.  
  7. DECLARE SUB MakeGif (a$, ScreenX%, ScreenY%, Xstart%, YStart%, Xend%,Yend%, NumColors%, AdaptorType%)
  8.  
  9. DECLARE SUB PutByte (a%)
  10.  
  11. DECLARE SUB PutCode (a%)
  12.  
  13. DECLARE SUB pal (c%, R%, G%, B%)
  14.  
  15. CONST TRUE = -1, FALSE = NOT TRUE
  16.  
  17.  
  18.  
  19. 'GS3DO.BAS by Matt Bross, 1997
  20.  
  21. 'The sorting algorithm was originally written by Ryan Wellman, which I
  22.  
  23. 'modified for my own purposes. I made the 3D program with help from
  24. '3D tutorials by Lithium /VLA, Shade3D.BAS by Rich Geldreich; and
  25. 'Gouraud fill with Luke Molnar's (of M/K Productions) gorau.bas. The GIF
  26. 'support is from Rich Geldreich's MakeGif.BAS.
  27.  
  28. '
  29.  
  30. 'completely RANDOMIZE
  31.  
  32. RANDOMIZE TIMER: DO: RANDOMIZE TIMER: LOOP UNTIL RND > .5
  33.  
  34. 'ON ERROR GOTO ErrorHandler
  35.  
  36. TYPE PointType
  37.  
  38.     x AS SINGLE 'X coordinate
  39.  
  40.     y AS SINGLE 'Y coordinate
  41.  
  42.     z AS SINGLE 'Z coordinate
  43.  
  44.     shade AS INTEGER 'shade of points
  45.  
  46.     dis AS SINGLE 'distance from the origin (0, 0, 0)
  47.  
  48. END TYPE
  49.  
  50. TYPE PolyType
  51.  
  52.     C1 AS INTEGER 'number of the first point of a polygon
  53.  
  54.     C2 AS INTEGER 'number of the second point of a polygon
  55.  
  56.     C3 AS INTEGER 'number of the third point of a polygon
  57.  
  58.     culled AS INTEGER 'TRUE if the polygon isn't visible
  59.  
  60.     AvgZ AS INTEGER 'used to sort Z coordinates of polygons
  61.  
  62. END TYPE
  63.  
  64. TYPE FillType
  65.  
  66.     Y1 AS INTEGER 'starting Y coordinate
  67.  
  68.     Y2 AS INTEGER 'ending Y coordinate
  69.  
  70.     clr1 AS INTEGER 'starting color
  71.  
  72.     clr2 AS INTEGER 'ending color
  73.  
  74. END TYPE
  75.  
  76. '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%INFO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  77.  
  78. SCREEN 0, 0, 0, 0: WIDTH 80, 25: CLS
  79.  
  80. PRINT "GS3DO.BAS by Matt Bross, 1997"
  81.  
  82. PRINT
  83.  
  84. PRINT "3D ANIMATION FOR THE QBASIC LANGUAGE"
  85.  
  86. PRINT "COPYRIGHT MATT BROSS. USE FREELY AS"
  87.  
  88. PRINT "LONG AS CREDIT IS GIVEN."
  89.  
  90. PRINT
  91.  
  92. PRINT "--------CONTROLS--------"
  93.  
  94. PRINT " 0 - reset rotation"
  95.  
  96. PRINT " 5 - stop rotation"
  97.  
  98. PRINT " S - reset location"
  99.  
  100. PRINT " A - stop translation"
  101.  
  102. PRINT "2, 8 - rotate around x axis"
  103.  
  104. PRINT "4, 6 - rotate around y axis"
  105.  
  106. PRINT "-, + - rotate around z axis"
  107.  
  108. PRINT CHR$(24); ", "; CHR$(25); " - translate vertically"
  109.  
  110. PRINT CHR$(27); ", "; CHR$(26); " - translate horizontally"
  111.  
  112. PRINT "Z, X - translate depthwise"
  113.  
  114. PRINT " Esc - exit"
  115.  
  116. PRINT "----CASE INSENSITIVE----"
  117.  
  118. PRINT
  119.  
  120. INPUT "OBJECT TO LOAD", file$
  121.  
  122. IF file$ = "" THEN file$ = "pyramid.txt"
  123.  
  124. '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%VARIABLES%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  125.  
  126. 'SRX = the screen's x resolution
  127.  
  128. 'SRY = the screen's y resolution
  129.  
  130. SRX = 320: SRY = 200
  131.  
  132. 'DX = the X coordinate of the center of the screen
  133.  
  134. 'DY = the Y coordinate of the center of the screen
  135.  
  136. DX = SRX \ 2: DY = SRY \ 2
  137.  
  138. 'D = the viewer's distance then object: SD = controls perspective
  139.  
  140. D = 350: SD = 140
  141.  
  142. 'MaxSpin = controls the maximum rotation speed
  143.  
  144. 'MaxSpeed = controls the maximum translation speed
  145.  
  146. MaxSpin = 20: MaxSpeed = 10
  147.  
  148. 'NumClr = the number of palette values to assign to shading each color
  149.  
  150. NumClr = 63
  151.  
  152. '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%GIF STUFF%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  153.  
  154. DIM SHARED OutBuffer$, OStartAddress, OAddress, OEndAddress, Oseg
  155.  
  156. DIM SHARED CodeSize, CurrentBit, Char&, BlockLength
  157.  
  158. DIM SHARED Shift(7) AS LONG
  159.  
  160. DIM SHARED x, y, Minx, MinY, MaxX, MaxY, Done, GIFFile, LastLoc&
  161.  
  162. ShiftTable:
  163.  
  164. DATA 1,2,4,8,16,32,64,128
  165.  
  166. '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%SIN TABLES%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  167.  
  168. 'create SINe and COSine tables for 360 degrees in radians, and then
  169.  
  170. 'scale 1024 times for faster math.
  171.  
  172. '$STATIC
  173.  
  174. DIM SINx(360) AS LONG, COSx(360) AS LONG
  175.  
  176. FOR i = 0 TO 360
  177.  
  178.     SINx(i) = SIN(i * (22 / 7) / 180) * 1024 'use 1024 to shift binary digits
  179.  
  180.     COSx(i) = COS(i * (22 / 7) / 180) * 1024 'over 6 bits.
  181.  
  182. NEXT i
  183.  
  184. '%%%%%%%%%%%%%%%%%%%%%%%%%%GOURAUD SHADE ARRAYS%%%%%%%%%%%%%%%%%%%%%%%%%
  185.  
  186. DIM scan(320) AS FillType 'DIM gouraud shading array
  187.  
  188. DIM coord(1 TO 3)
  189.  
  190. '%%%%%%%%%%%%%%%%%%%%%%%%DOUBLE BUFFERING ARRAYS%%%%%%%%%%%%%%%%%%%%%%%%
  191.  
  192. DIM SHARED aofs&
  193.  
  194. DIM SHARED ScnBuf(32001) 'DIM array to serve as page in SCREEN 13
  195.  
  196. ScnBuf(0) = 320 * 8 'set length (x)
  197.  
  198. ScnBuf(1) = 200 'set height (y)
  199.  
  200. DEF SEG = VARSEG(ScnBuf(2)) 'get segment of beginning of array data
  201.  
  202. aofs& = VARPTR(ScnBuf(2)) 'get offset of beginning of array data
  203.  
  204. '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%LIGHT TABLES%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  205.  
  206. DIM LX(256), LY(256), LZ(256)
  207.  
  208. 'Location of light source in spherical coordinates
  209.  
  210. l1 = 70: l2 = 40: a1! = l1 / 57.29: a2! = l2 / 57.29
  211.  
  212. s1! = SIN(a1!): C1! = COS(a1!): s2! = SIN(a2!): C2! = COS(a2!)
  213.  
  214. LX = 128 * s1! * C2!: LY = 128 * s1! * s2!: LZ = 128 * C1!
  215.  
  216. 'find length of segment from light source to origin (0, 0, 0)
  217.  
  218. ldis! = SQR(LX * LX + LY * LY + LZ * LZ) / 128
  219.  
  220. FOR a = -128 TO 128
  221.  
  222.     LX(a + 128) = LX * a 'make light source lookup tables for shading
  223.  
  224.     LY(a + 128) = LY * a
  225.  
  226.     LZ(a + 128) = LZ * a
  227.  
  228. NEXT a
  229.  
  230. '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%LOAD OBJECT%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  231.  
  232. OPEN file$ FOR INPUT AS #1
  233.  
  234. 'Load Points Data
  235.  
  236. INPUT #1, MaxPoints, MaxPolys
  237.  
  238. DIM POINTS(MaxPoints) AS PointType 'at start
  239.  
  240. DIM ScnPnts(MaxPoints) AS PointType 'after rotation
  241.  
  242. DIM SX(MaxPoints), SY(MaxPoints) 'points drawn to screen
  243.  
  244. FOR i = 1 TO MaxPoints
  245.  
  246.     INPUT #1, x!, y!, z!: POINTS(i).x = x!: POINTS(i).y = y!: POINTS(i).z = z!
  247.  
  248.     'find distance from point to the origin (0, 0, 0)
  249.  
  250.     dis! = SQR(x! * x! + y! * y! + z! * z!)
  251.  
  252.     POINTS(i).dis = dis! * ldis!: ScnPnts(i).dis = dis! * ldis!
  253.  
  254. NEXT i
  255.  
  256. 'Load Polygon Data
  257.  
  258. DIM SHARED P(MaxPolys) AS PolyType 'stores all polygon data
  259.  
  260. FOR i = 1 TO MaxPolys
  261.  
  262.     INPUT #1, P(i).C1, P(i).C2, P(i).C3
  263.  
  264. NEXT i: CLOSE
  265.  
  266. PRINT "Press a Key"
  267.  
  268. DO: LOOP UNTIL INKEY$ <> ""
  269.  
  270. '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%SET PALETTE%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  271.  
  272. SCREEN 13: CLS
  273.  
  274. s! = 0: ClrStep! = 63 / NumClr
  275.  
  276. FOR a = 0 TO NumClr
  277.  
  278.     pal a, c, c, c
  279.  
  280.     s! = s! + ClrStep!: c = INT(s!)
  281.  
  282. NEXT a
  283.  
  284. '%%%%%%%%%%%%%%%%%%%%%%%%%%%LOOK UP VARIABLES%%%%%%%%%%%%%%%%%%%%%%%%%%%
  285.  
  286. ZERO = 0: ONE = 1: THREE6D = 360
  287.  
  288. '------------------------------>BEGIN MAIN LOOP<------------------------
  289.  
  290. DO
  291.  
  292.     '*********************************GET KEY*******************************
  293.  
  294.     k$ = UCASE$(INKEY$)
  295.  
  296.     SELECT CASE k$
  297.  
  298.         CASE "0"
  299.  
  300.             R1 = ZERO: R2 = ZERO: R3 = ZERO
  301.  
  302.             D1 = ZERO: D2 = ZERO: D3 = ZERO
  303.  
  304.         CASE "5"
  305.  
  306.             D1 = ZERO: D2 = ZERO: D3 = ZERO
  307.  
  308.         CASE "A"
  309.  
  310.             MX = ZERO: MY = ZERO: MZ = ZERO
  311.  
  312.         CASE "S"
  313.  
  314.             MX = ZERO: MY = ZERO: MZ = ZERO
  315.  
  316.             MMX = ZERO: MMY = ZERO: MMZ = ZERO
  317.  
  318.         CASE "2"
  319.  
  320.             D1 = D1 - ONE
  321.  
  322.         CASE "8"
  323.  
  324.             D1 = D1 + ONE
  325.  
  326.         CASE "4"
  327.  
  328.             D2 = D2 - ONE
  329.  
  330.         CASE "6"
  331.  
  332.             D2 = D2 + ONE
  333.  
  334.         CASE "+", "="
  335.  
  336.             D3 = D3 - ONE
  337.  
  338.         CASE "-"
  339.  
  340.             D3 = D3 + ONE
  341.  
  342.         CASE CHR$(0) + "H"
  343.  
  344.             MY = MY + ONE
  345.  
  346.         CASE CHR$(0) + "P"
  347.  
  348.             MY = MY - ONE
  349.  
  350.         CASE CHR$(0) + "K"
  351.  
  352.             MX = MX + ONE
  353.  
  354.         CASE CHR$(0) + "M"
  355.  
  356.             MX = MX - ONE
  357.  
  358.         CASE "Z"
  359.  
  360.             MZ = MZ + ONE
  361.  
  362.         CASE "X"
  363.  
  364.             MZ = MZ - ONE
  365.  
  366.         CASE CHR$(27)
  367.  
  368.             GOTO ShutDown
  369.  
  370.         CASE "G"
  371.  
  372.             file$ = "pyramid.gif"
  373.  
  374.  
  375.             IF RIGHT$(UCASE$(file$), 4) <> ".GIF" THEN file$ = file$ + ".GIF"
  376.  
  377.             PUT (0, 0), ScnBuf(), PSET
  378.  
  379.             MakeGif file$, 320, 200, 0, 0, 319, 199, 256, 2
  380.  
  381.     END SELECT
  382.  
  383.     '*********************************ROTATION******************************
  384.  
  385.     'keep rotation speed under control
  386.  
  387.     IF D1 > MaxSpin THEN D1 = MaxSpin
  388.  
  389.     IF D2 > MaxSpin THEN D2 = MaxSpin
  390.  
  391.     IF D2 > MaxSpin THEN D2 = MaxSpin
  392.  
  393.     IF D1 < -MaxSpin THEN D1 = -MaxSpin
  394.  
  395.     IF D2 < -MaxSpin THEN D2 = -MaxSpin
  396.  
  397.     IF D2 < -MaxSpin THEN D2 = -MaxSpin
  398.  
  399.     'keep SINes and COSines in array limits
  400.  
  401.     R1 = (R1 + D1) MOD THREE6D: IF R1 < ZERO THEN R1 = THREE6D + R1
  402.  
  403.     R2 = (R2 + D2) MOD THREE6D: IF R2 < ZERO THEN R2 = THREE6D + R2
  404.  
  405.     R3 = (R3 + D3) MOD THREE6D: IF R3 < ZERO THEN R3 = THREE6D + R3
  406.  
  407.     '********************************TRANSLATION****************************
  408.  
  409.     'Keep translation speed from becoming uncontrollable
  410.  
  411.     IF MX > MaxSpeed THEN MX = MaxSpeed
  412.  
  413.     IF MY > MaxSpeed THEN MY = MaxSpeed
  414.  
  415.     IF MZ > MaxSpeed THEN MZ = MaxSpeed
  416.  
  417.     IF MX < -MaxSpeed THEN MX = -MaxSpeed
  418.  
  419.     IF MY < -MaxSpeed THEN MY = -MaxSpeed
  420.  
  421.     IF MZ < -MaxSpeed THEN MZ = -MaxSpeed
  422.  
  423.     MMX = MMX + MX: MMY = MMY + MY: MMZ = MMZ + MZ
  424.  
  425.     'Keeps variables within limits of integers
  426.  
  427.     IF MMX > 32767 THEN MMX = 32767
  428.  
  429.     IF MMY > 250 THEN MMY = 250
  430.  
  431.     IF MMZ > 120 THEN MMZ = 120
  432.  
  433.     IF MMX < -32767 THEN MMX = -32767
  434.  
  435.     IF MMY < -120 THEN MMY = -120
  436.  
  437.     IF MMZ < -327 THEN MMZ = -327
  438.  
  439.     '*******************************MOVE OBJECT*****************************
  440.  
  441.     FOR i = 1 TO MaxPoints
  442.  
  443.         'rotate points around the Y axis
  444.  
  445.         TEMPX = (POINTS(i).x * COSx(R2) - POINTS(i).z * SINx(R2)) \ 1024
  446.  
  447.         TEMPZ = (POINTS(i).x * SINx(R2) + POINTS(i).z * COSx(R2)) \ 1024
  448.  
  449.         'rotate points around the X axis
  450.  
  451.         ScnPnts(i).z = (TEMPZ * COSx(R1) - POINTS(i).y * SINx(R1)) \ 1024
  452.  
  453.         TEMPY = (TEMPZ * SINx(R1) + POINTS(i).y * COSx(R1)) \ 1024
  454.  
  455.         'rotate points around the Z axis
  456.  
  457.         ScnPnts(i).x = (TEMPX * COSx(R3) + TEMPY * SINx(R3)) \ 1024
  458.  
  459.         ScnPnts(i).y = (TEMPY * COSx(R3) - TEMPX * SINx(R3)) \ 1024
  460.  
  461.         '******************************CONVERT 3D TO 2D*************************
  462.  
  463.         TEMPZ = ScnPnts(i).z + MMZ - SD
  464.  
  465.         IF TEMPZ < ZERO THEN 'only calculate points visible by viewer
  466.  
  467.             SX(i) = (D * ((ScnPnts(i).x + MMX) / TEMPZ)) + DX
  468.  
  469.             SY(i) = (D * ((ScnPnts(i).y + MMY) / TEMPZ)) + DY
  470.  
  471.         END IF
  472.  
  473.         '*******************************SHADE POINTS****************************
  474.  
  475.         X1 = ScnPnts(i).x: Y1 = ScnPnts(i).y: Z1 = ScnPnts(i).z
  476.  
  477.         s = CINT((X1 * LX + Y1 * LY + Z1 * LZ) \ ScnPnts(i).dis) + 128
  478.  
  479.         IF s < ZERO THEN s = ZERO
  480.  
  481.         IF s > 256 THEN s = 256
  482.  
  483.         shade = (LX(s) + LY(s) + LZ(s)) \ 3
  484.  
  485.         IF shade < ZERO THEN shade = ZERO
  486.  
  487.         IF shade > NumClr THEN shade = NumClr
  488.  
  489.         ScnPnts(i).shade = shade
  490.  
  491.     NEXT
  492.  
  493.     FOR i = 1 TO MaxPolys
  494.  
  495.         '*************************CULL NON-VISIABLE POLYGONS********************
  496.  
  497.         'this isn't perfect yet so I REMmed it, so for more speed unrem the following
  498.  
  499.         coord(1) = P(i).C1: coord(2) = P(i).C2: coord(3) = P(i).C3
  500.  
  501.         X1 = ScnPnts(coord(1)).x: X2 = ScnPnts(coord(2)).x: X3 = ScnPnts(coord(3)).x
  502.  
  503.         Y1 = ScnPnts(coord(1)).y: Y2 = ScnPnts(coord(2)).y: Y3 = ScnPnts(coord(3)).y
  504.  
  505.         Z1 = ScnPnts(coord(1)).z: Z2 = ScnPnts(coord(2)).z: Z3 = ScnPnts(coord(3)).z
  506.  
  507.         cull1 = X3 * ((Y1 * Z2) - (Z1 * Y2)): cull2 = Y3 * ((X1 * Z2) - (Z1 * X2))
  508.  
  509.         cull3 = Z3 * ((X1 * Y2) - (Y1 * X2))
  510.  
  511.         IF cull1 + cull2 + cull3 = ZERO THEN P(i).culled = TRUE ELSE P(i).culled = FALSE
  512.  
  513.         '******************FIND AVERAGE Z COORDINATE OF EACH POLYGON************
  514.  
  515.         P(i).AvgZ = (Z1 + Z2 + Z3) \ 3
  516.  
  517.     NEXT i
  518.  
  519.     '******************SORT POLGONS BY THEIR AVERAGE Z COORDINATE***********
  520.  
  521.     increment = MaxPolys + 1
  522.  
  523.     DO
  524.  
  525.         increment = increment \ 2
  526.  
  527.         FOR index = 1 TO MaxPolys - increment
  528.  
  529.             IF P(index).AvgZ > P(index + increment).AvgZ THEN
  530.  
  531.                 SWAP P(index), P(index + increment)
  532.  
  533.                 IF index > increment THEN
  534.  
  535.                     cutpoint = index
  536.  
  537.                     DO
  538.  
  539.                         index = (index - increment): IF index < 1 THEN index = 1
  540.  
  541.                         IF P(index).AvgZ > P(index + increment).AvgZ THEN
  542.  
  543.                             SWAP P(index), P(index + increment)
  544.  
  545.                         ELSE
  546.  
  547.                             index = cutpoint: EXIT DO
  548.  
  549.                         END IF
  550.  
  551.                     LOOP
  552.  
  553.                 END IF
  554.  
  555.             END IF
  556.  
  557.         NEXT index
  558.  
  559.     LOOP UNTIL increment <= 1
  560.  
  561.     '******************************DRAW POLYGONS****************************
  562.  
  563.     'clear screen buffer. Use a 320 by 200 BLOADable graphic for a background.
  564.  
  565.     ERASE ScnBuf: ScnBuf(0) = 2560: ScnBuf(1) = SRY
  566.  
  567.  
  568.  
  569.     FOR i = 1 TO MaxPolys
  570.  
  571.         IF P(i).culled = FALSE THEN
  572.  
  573.             'load points
  574.  
  575.             coord(1) = P(i).C1: coord(2) = P(i).C2: coord(3) = P(i).C3
  576.  
  577.             'find highest and lowest Y coordinates
  578.  
  579.             xmin = SRX: xmax = ZERO
  580.  
  581.             IF SX(coord(1)) > xmax THEN xmax = SX(coord(1))
  582.  
  583.             IF SX(coord(2)) > xmax THEN xmax = SX(coord(2))
  584.  
  585.             IF SX(coord(3)) > xmax THEN xmax = SX(coord(3))
  586.  
  587.             IF SX(coord(1)) < xmin THEN xmin = SX(coord(1))
  588.  
  589.             IF SX(coord(2)) < xmin THEN xmin = SX(coord(2))
  590.  
  591.             IF SX(coord(3)) < xmin THEN xmin = SX(coord(3))
  592.  
  593.             'keep min's and max's in the limits of the screen
  594.  
  595.             IF xmin < ZERO THEN xmin = ZERO
  596.  
  597.             IF xmax > SRX THEN xmax = SRX
  598.  
  599.             IF xmin > SRX THEN EXIT FOR
  600.  
  601.             IF xmax < ZERO THEN EXIT FOR
  602.  
  603.             IF SY(coord(1)) AND SY(coord(2)) AND SY(coord(3)) < ZERO THEN EXIT FOR
  604.  
  605.             IF SY(coord(1)) AND SY(coord(2)) AND SY(coord(3)) > SRY THEN EXIT FOR
  606.  
  607.  
  608.  
  609.             ERASE scan
  610.  
  611.  
  612.  
  613.             FOR j = 1 TO 3
  614.  
  615.                 k = j + 1: IF k > 3 THEN k = 1
  616.  
  617.                 VAL1 = coord(j): VAL2 = coord(k)
  618.  
  619.                 IF SX(VAL1) > SX(VAL2) THEN SWAP VAL1, VAL2
  620.  
  621.                 Y1 = SY(VAL1): X1 = SX(VAL1): Y2 = SY(VAL2): X2 = SX(VAL2)
  622.  
  623.                 col1 = ScnPnts(VAL1).shade: Col2 = ScnPnts(VAL2).shade
  624.  
  625.                 XDelta = X2 - X1: YDelta = Y2 - Y1: CDelta = Col2 - col1
  626.  
  627.                 IF XDelta <> ZERO THEN
  628.  
  629.                     YSlope = (YDelta / XDelta) * 128
  630.  
  631.                     CSlope = (CDelta / XDelta) * 128
  632.  
  633.                 ELSE
  634.  
  635.                     YSlope = ZERO
  636.  
  637.                     CSlope = ZERO
  638.  
  639.                 END IF
  640.  
  641.  
  642.  
  643.                 YVal& = Y1 * 128: CVal& = col1 * 128
  644.  
  645.                 IF X1 < ZERO THEN X1 = ZERO
  646.  
  647.                 IF X2 > SRX THEN X2 = SRX
  648.  
  649.  
  650.  
  651.                 FOR f = X1 TO X2
  652.  
  653.                     IF scan(f).Y1 = ZERO THEN
  654.  
  655.                         scan(f).Y1 = YVal& \ 128
  656.  
  657.                         scan(f).clr1 = CVal& \ 128
  658.  
  659.                     ELSE
  660.  
  661.                         scan(f).Y2 = YVal& \ 128
  662.  
  663.                         scan(f).clr2 = CVal& \ 128
  664.  
  665.                     END IF
  666.  
  667.                     YVal& = YVal& + YSlope
  668.  
  669.                     CVal& = CVal& + CSlope
  670.  
  671.                 NEXT f
  672.  
  673.             NEXT j
  674.  
  675.  
  676.  
  677.             FOR f = xmin TO xmax
  678.  
  679.  
  680.  
  681.                 IF scan(f).Y1 > scan(f).Y2 THEN
  682.  
  683.                     Y1 = scan(f).Y2: Y2 = scan(f).Y1
  684.  
  685.                     col1 = scan(f).clr2: Col2 = scan(f).clr1
  686.  
  687.                 ELSE
  688.  
  689.                     Y1 = scan(f).Y1: Y2 = scan(f).Y2
  690.  
  691.                     col1 = scan(f).clr1: Col2 = scan(f).clr2
  692.  
  693.                 END IF
  694.  
  695.  
  696.  
  697.                 YDelta = Y2 - Y1: CDelta = Col2 - col1
  698.  
  699.                 IF YDelta = ZERO THEN YDelta = 1
  700.  
  701.                 CSlope = (CDelta / YDelta) * 128: CVal& = col1 * 128
  702.  
  703.  
  704.  
  705.                 FOR j = scan(f).Y1 TO scan(f).Y2
  706.  
  707.                     'clip polygon to screen (set boundaries)
  708.  
  709.                     IF f < SRX AND f > ZERO AND j > ZERO AND j < SRY THEN
  710.  
  711.                         pixel = CVal& \ 128: IF pixel > NumClr THEN pixel = NumClr
  712.  
  713.                         'write pixel to screen buffer
  714.  
  715.                         POKE aofs& + f + j * 320&, pixel
  716.  
  717.                     END IF
  718.  
  719.                     CVal& = CVal& + CSlope
  720.  
  721.                 NEXT j
  722.  
  723.             NEXT f
  724.  
  725.         END IF
  726.  
  727.     NEXT i
  728.  
  729.  
  730.  
  731.     PUT (ZERO, ZERO), ScnBuf(), PSET 'dump array to screen, like PCOPY
  732.  
  733.     '******************************FRAME COUNTER****************************
  734.  
  735.     'LOCATE 1, 1: PRINT fps: frame = frame + 1
  736.  
  737.     'LOCATE 2, 1: PRINT TIMER - D#: D# = TIMER
  738.  
  739.     'IF TIMER > t# THEN t# = TIMER + 1: fps = frame: frame = zero
  740.  
  741. LOOP
  742.  
  743. '------------------------------>END MAIN LOOP<--------------------------
  744.  
  745. ShutDown:
  746.  
  747. DEF SEG
  748.  
  749. SCREEN 0, 0, 0, 0: WIDTH 80, 25: CLS
  750.  
  751. PRINT "GS3DO.BAS by Matt Bross, 1997"
  752.  
  753. PRINT: PRINT "THERE WERE"; MaxPoints; "POINTS AND"; MaxPolys; "POLYGONS"
  754.  
  755. PRINT: PRINT "Free space"
  756.  
  757. PRINT " String Array Stack"
  758.  
  759. PRINT STRING$(21, "-")
  760.  
  761. 'PRINT FRE(""); FRE(-1); FRE(-2): END
  762.  
  763. END
  764. RETURN
  765. ErrorHandler:
  766.  
  767. RESUME NEXT
  768.  
  769.  
  770.  
  771. 'Puts a byte into the disk buffer... when the disk buffer is full it is
  772.  
  773. 'dumped to disk.
  774.  
  775. SUB BufferWrite (a) STATIC
  776.  
  777.  
  778.  
  779. IF OAddress = OEndAddress THEN 'are we at the end of the buffer?
  780.  
  781.     PUT GIFFile, , OutBuffer$ ' yup, write it out and
  782.  
  783.     OAddress = OStartAddress ' start all over
  784.  
  785. END IF
  786.  
  787. POKE OAddress, a 'put byte in buffer
  788.  
  789. OAddress = OAddress + 1 'increment position
  790.  
  791. END SUB
  792.  
  793.  
  794.  
  795. 'This routine gets one pixel from the display.
  796.  
  797. FUNCTION GetByte STATIC
  798.  
  799.  
  800.  
  801. GetByte = POINT(x, y) 'get the "byte"
  802.  
  803. x = x + 1 'increment X coordinate
  804.  
  805. IF x > MaxX THEN 'are we too far?
  806.  
  807.     LINE (Minx, y)-(MaxX, y), 0 'a pacifier for impatient users
  808.  
  809.     x = Minx 'go back to start
  810.  
  811.     y = y + 1 'increment Y coordinate
  812.  
  813.     IF y > MaxY THEN 'are we too far down?
  814.  
  815.         Done = TRUE ' yup, flag it then
  816.  
  817.     END IF
  818.  
  819. END IF
  820.  
  821. END FUNCTION
  822.  
  823.  
  824.  
  825. '
  826.  
  827. '-----------------------------------------------------------------------
  828.  
  829. ' PDS 7.1 & QB4.5 GIF Compression Routine v1.00 By Rich Geldreich 1992
  830.  
  831. '-----------------------------------------------------------------------
  832.  
  833. '
  834.  
  835. 'A$ = output filename
  836.  
  837. 'ScreenX = X resolution of screen(320, 640, etc.)
  838.  
  839. 'ScreenY = Y resolution of screen(200, 350, 480, etc.)
  840.  
  841. 'XStart = <-upper left hand corner of area to encode
  842.  
  843. 'YStart = < " "
  844.  
  845. 'Xend = <-lower right hand corner of area to encode
  846.  
  847. 'Yend = < " "
  848.  
  849. 'NumColors = # of colors on screen(2, 16, 256)
  850.  
  851. 'AdaptorType = 1 for EGA 2 for VGA
  852.  
  853. 'NOTE: EGA palettes are not supported in this version of MakeGIF.
  854.  
  855. '
  856.  
  857. SUB MakeGif (a$, ScreenX, ScreenY, Xstart, YStart, Xend, Yend, NumColors, AdaptorType)
  858. _TITLE "makegif"
  859. 'hash table's size - must be a prime number!
  860.  
  861. CONST Table.Size = 7177
  862.  
  863.  
  864.  
  865. DIM Prefix(Table.Size - 1), Suffix(Table.Size - 1), code(Table.Size - 1)
  866.  
  867.  
  868.  
  869. 'The shift table contains the powers of 2 needed by the
  870.  
  871. 'PutCode routine. This is done for speed. (much faster to
  872.  
  873. 'look up an integer than to perform calculations...)
  874.  
  875. RESTORE ShiftTable
  876.  
  877. FOR a = 0 TO 7: READ Shift(a): NEXT
  878.  
  879.  
  880.  
  881. 'MinX, MinY, MaxX, MaxY have the encoding window
  882.  
  883. Minx = Xstart: MinY = YStart
  884.  
  885. MaxX = Xend: MaxY = Yend
  886.  
  887.  
  888.  
  889. 'Open GIF output file
  890.  
  891. GIFFile = FREEFILE 'use next free file
  892.  
  893. OPEN a$ FOR BINARY AS GIFFile
  894.  
  895.  
  896.  
  897. 'Put GIF87a header at beginning of file
  898.  
  899. B$ = "GIF87a"
  900.  
  901. PUT GIFFile, , B$
  902.  
  903.  
  904.  
  905. 'See how many colors are in this image...
  906.  
  907. SELECT CASE NumColors
  908.  
  909.     CASE 2 'monochrome image
  910.  
  911.         BitsPixel = 1 '1 bit per pixel
  912.  
  913.         StartSize = 3 'first LZW code is 3 bits
  914.  
  915.         StartCode = 4 'first free code
  916.  
  917.         StartMax = 8 'maximum code in 3 bits
  918.  
  919.  
  920.  
  921.     CASE 16 '16 colors images
  922.  
  923.         BitsPixel = 4 '4 bits per pixel
  924.  
  925.         StartSize = 5 'first LZW code is 5 bits
  926.  
  927.         StartCode = 16 'first free code
  928.  
  929.         StartMax = 32 'maximum code in 5 bits
  930.  
  931.  
  932.  
  933.     CASE 256 '256 color images
  934.  
  935.         BitsPixel = 8 '8 bits per pixel
  936.  
  937.         StartSize = 9 'first LZW code is 9 bits
  938.  
  939.         StartCode = 256 'first free code
  940.  
  941.         StartMax = 512 'maximum code in 9 bits
  942.  
  943. END SELECT
  944.  
  945. 'This following routine probably isn't needed- I've never
  946.  
  947. 'had to use the "ColorBits" variable... With the EGA, you
  948.  
  949. 'have 2 bits for Red, Green, & Blue. With VGA, you have 6 bits.
  950.  
  951. SELECT CASE AdaptorType
  952.  
  953.     CASE 1
  954.  
  955.         ColorBits = 2 'EGA
  956.  
  957.     CASE 2
  958.  
  959.         ColorBits = 6 'VGA
  960.  
  961. END SELECT
  962.  
  963.  
  964.  
  965. PUT GIFFile, , ScreenX 'put screen's dimensions
  966.  
  967. PUT GIFFile, , ScreenY
  968.  
  969. 'pack colorbits and bits per pixel
  970.  
  971. a = 128 + (ColorBits - 1) * 16 + (BitsPixel - 1)
  972.  
  973. PUT GIFFile, , a
  974.  
  975. 'throw a zero into the GIF file
  976.  
  977. a$ = CHR$(0)
  978.  
  979. PUT GIFFile, , a$
  980.  
  981. 'Get the RGB palette from the screen and put it into the file...
  982.  
  983. SELECT CASE AdaptorType
  984.  
  985.     CASE 1
  986.  
  987.         STOP
  988.  
  989.         'EGA palette routine not implemented yet
  990.  
  991.     CASE 2
  992.  
  993.         OUT &H3C7, 0
  994.  
  995.         FOR a = 0 TO NumColors - 1
  996.  
  997.             'Note: a BIOS call could be used here, but then we have to use
  998.  
  999.             'the messy CALL INTERRUPT subs...
  1000.  
  1001.             R = (INP(&H3C9) * 65280) \ 16128 'C=R * 4.0476190(for 0-255)
  1002.  
  1003.             G = (INP(&H3C9) * 65280) \ 16128
  1004.  
  1005.             B = (INP(&H3C9) * 65280) \ 16128
  1006.  
  1007.             a$ = CHR$(R): PUT GIFFile, , a$
  1008.  
  1009.             a$ = CHR$(G): PUT GIFFile, , a$
  1010.  
  1011.             a$ = CHR$(B): PUT GIFFile, , a$
  1012.  
  1013.         NEXT
  1014.  
  1015. END SELECT
  1016.  
  1017.  
  1018.  
  1019. 'write out an image descriptor...
  1020.  
  1021. a$ = "," '"," is image seperator
  1022.  
  1023. PUT GIFFile, , a$ 'write it
  1024.  
  1025. PUT GIFFile, , Minx 'write out the image's location
  1026.  
  1027. PUT GIFFile, , MinY
  1028.  
  1029. ImageWidth = (MaxX - Minx + 1) 'find length & width of image
  1030.  
  1031. ImageHeight = (MaxY - MinY + 1)
  1032.  
  1033. PUT GIFFile, , ImageWidth 'store them into the file
  1034.  
  1035. PUT GIFFile, , ImageHeight
  1036.  
  1037. a$ = CHR$(BitsPixel - 1) '# bits per pixel in the image
  1038.  
  1039. PUT GIFFile, , a$
  1040.  
  1041.  
  1042.  
  1043. a$ = CHR$(StartSize - 1) 'store the LZW minimum code size
  1044.  
  1045. PUT GIFFile, , a$
  1046.  
  1047.  
  1048.  
  1049. 'Initialize the vars needed by PutCode
  1050.  
  1051. CurrentBit = 0: Char& = 0
  1052.  
  1053.  
  1054.  
  1055. MaxCode = StartMax 'the current maximum code size
  1056.  
  1057. CodeSize = StartSize 'the current code size
  1058.  
  1059. ClearCode = StartCode 'ClearCode & EOF code are the
  1060.  
  1061. EOFCode = StartCode + 1 ' first two entries
  1062.  
  1063. StartCode = StartCode + 2 'first free code that can be used
  1064.  
  1065. NextCode = StartCode 'the current code
  1066.  
  1067.  
  1068.  
  1069. OutBuffer$ = STRING$(5000, 32) 'output buffer; for speedy disk writes
  1070.  
  1071. a& = SADD(OutBuffer$) 'find address of buffer
  1072.  
  1073. a& = a& - 65536 * (a& < 0)
  1074.  
  1075. Oseg = VARSEG(OutBuffer$) + (a& \ 16) 'get segment + offset >> 4
  1076.  
  1077. OAddress = a& AND 15 'get address into segment
  1078.  
  1079. OEndAddress = OAddress + 5000 'end of disk buffer
  1080.  
  1081. OStartAddress = OAddress 'current location in disk buffer
  1082.  
  1083. DEF SEG = Oseg
  1084.  
  1085.  
  1086.  
  1087. GOSUB ClearTree 'clear the tree & output a
  1088.  
  1089. PutCode ClearCode ' clear code
  1090.  
  1091.  
  1092.  
  1093. x = Xstart: y = YStart 'X & Y have the current pixel
  1094.  
  1095. Prefix = GetByte 'the first pixel is a special case
  1096.  
  1097. Done = FALSE 'True when image is complete
  1098.  
  1099.  
  1100.  
  1101. DO 'while there are more pixels to encode
  1102.  
  1103.  
  1104.  
  1105.     DO 'until we have a new string to put into the table
  1106.  
  1107.  
  1108.  
  1109.         IF Done THEN 'write out the last pixel, clear the disk buffer
  1110.  
  1111.             'and fix up the last block so its count is correct
  1112.  
  1113.  
  1114.  
  1115.             PutCode Prefix 'write last pixel
  1116.  
  1117.             PutCode EOFCode 'send EOF code
  1118.  
  1119.  
  1120.  
  1121.             IF CurrentBit <> 0 THEN
  1122.  
  1123.                 PutCode 0 'flush out the last code...
  1124.  
  1125.             END IF
  1126.  
  1127.             PutByte 0
  1128.  
  1129.  
  1130.  
  1131.             OutBuffer$ = LEFT$(OutBuffer$, OAddress - OStartAddress)
  1132.  
  1133.             PUT GIFFile, , OutBuffer$
  1134.  
  1135.             a$ = ";" + STRING$(8, &H1A) 'the 8 EOF chars is not standard,
  1136.  
  1137.             'but many GIF's have them, so how
  1138.  
  1139.             'much could it hurt?
  1140.  
  1141.             PUT GIFFile, , a$
  1142.  
  1143.  
  1144.  
  1145.             a$ = CHR$(255 - BlockLength) 'correct the last block's count
  1146.  
  1147.             PUT GIFFile, LastLoc&, a$
  1148.  
  1149.  
  1150.  
  1151.             CLOSE GIFFile
  1152.  
  1153.             EXIT SUB
  1154.  
  1155.         ELSE 'get a pixel from the screen and see if we can find
  1156.  
  1157.             'the new string in the table
  1158.  
  1159.             Suffix = GetByte
  1160.  
  1161.             GOSUB Hash 'is it there?
  1162.  
  1163.             IF Found = TRUE THEN Prefix = code(index) 'yup, replace the
  1164.  
  1165.             'prefix:suffix string with whatever
  1166.  
  1167.             'code represents it in the table
  1168.  
  1169.         END IF
  1170.  
  1171.     LOOP WHILE Found 'don't stop unless we find a new string
  1172.  
  1173.  
  1174.  
  1175.     PutCode Prefix 'output the prefix to the file
  1176.  
  1177.  
  1178.  
  1179.     Prefix(index) = Prefix 'put the new string in the table
  1180.  
  1181.     Suffix(index) = Suffix
  1182.  
  1183.     code(index) = NextCode 'we've got to keep track if what code this is!
  1184.  
  1185.  
  1186.  
  1187.     Prefix = Suffix 'Prefix=the last pixel pulled from the screen
  1188.  
  1189.  
  1190.  
  1191.     NextCode = NextCode + 1 'get ready for the next code
  1192.  
  1193.     IF NextCode = MaxCode + 1 THEN 'can an output code ever exceed
  1194.  
  1195.         'the current code size?
  1196.  
  1197.         'yup, increase the code size
  1198.  
  1199.  
  1200.  
  1201.         MaxCode = MaxCode * 2
  1202.  
  1203.  
  1204.  
  1205.         'Note: The GIF89a spec mentions something about a deferred clear
  1206.  
  1207.         'code. When the clear code is deferred, codes are not entered
  1208.  
  1209.         'into the hash table anymore. When the compression of the image
  1210.  
  1211.         'starts to fall below a certain threshold, the clear code is
  1212.  
  1213.         'sent and the hash table is cleared. The overall result is
  1214.  
  1215.         'greater compression, because the table is cleared less often.
  1216.  
  1217.         'This version of MakeGIF doesn't support this, because some GIF
  1218.  
  1219.         'decoders crash when they attempt to enter too many codes
  1220.  
  1221.         'into the string table.
  1222.  
  1223.  
  1224.  
  1225.         IF CodeSize = 12 THEN 'is the code size too big?
  1226.  
  1227.             PutCode ClearCode 'yup; clear the table and
  1228.  
  1229.             GOSUB ClearTree 'start over
  1230.  
  1231.             NextCode = StartCode
  1232.  
  1233.             CodeSize = StartSize
  1234.  
  1235.             MaxCode = StartMax
  1236.  
  1237.  
  1238.  
  1239.  
  1240.  
  1241.         ELSE
  1242.  
  1243.             CodeSize = CodeSize + 1 'just increase the code size if
  1244.  
  1245.         END IF 'it's not too high( not > 12)
  1246.  
  1247.     END IF
  1248.  
  1249.  
  1250.  
  1251. LOOP 'while we have more pixels
  1252.  
  1253. ClearTree:
  1254.  
  1255. FOR a = 0 TO Table.Size - 1 'clears the hashing table
  1256.  
  1257.     Prefix(a) = -1 '-1 = invalid entry
  1258.  
  1259.     Suffix(a) = -1
  1260.  
  1261.     code(a) = -1
  1262.  
  1263. NEXT
  1264.  
  1265. RETURN
  1266.  
  1267. 'this is only one of a plethora of ways to search the table for
  1268.  
  1269. 'a match! I used a binary tree first, but I switched to hashing
  1270.  
  1271. 'cause it's quicker(perhaps the way I implemented the tree wasn't
  1272.  
  1273. 'optimal... who knows!)
  1274.  
  1275. Hash:
  1276.  
  1277. 'hash the prefix & suffix(there are also many ways to do this...)
  1278.  
  1279. '?? is there a better formula?
  1280.  
  1281. index = ((Prefix * 256&) XOR Suffix) MOD Table.Size
  1282.  
  1283. '
  1284.  
  1285. '(Note: the table size(7177 in this case) must be a prime number, or
  1286.  
  1287. 'else there's a chance that the routine will hang up... hate when
  1288.  
  1289. 'that happens!)
  1290.  
  1291. '
  1292.  
  1293. 'Calculate an offset just in case we don't find what we want on the
  1294.  
  1295. 'first try...
  1296.  
  1297. IF index = 0 THEN 'can't have Table.Size-0 !
  1298.  
  1299.     Offset = 1
  1300.  
  1301. ELSE
  1302.  
  1303.     Offset = Table.Size - index
  1304.  
  1305. END IF
  1306.  
  1307.  
  1308.  
  1309. DO 'until we (1) find an empty entry or (2) find what we're lookin for
  1310.  
  1311.  
  1312.  
  1313.  
  1314.  
  1315.     IF code(index) = -1 THEN 'is this entry blank?
  1316.  
  1317.         Found = FALSE 'yup- we didn't find the string
  1318.  
  1319.         RETURN
  1320.  
  1321.         'is this entry the one we're looking for?
  1322.  
  1323.     ELSEIF Prefix(index) = Prefix AND Suffix(index) = Suffix THEN
  1324.  
  1325.         'yup, congrats you now understand hashing!!!
  1326.  
  1327.  
  1328.  
  1329.         Found = TRUE
  1330.  
  1331.         RETURN
  1332.  
  1333.     ELSE
  1334.  
  1335.         'shoot! we didn't find anything interesting, so we must
  1336.  
  1337.         'retry- this is what slows hashing down. I could of used
  1338.  
  1339.         'a bigger table, that would of speeded things up a little
  1340.  
  1341.         'because this retrying would not happen as often...
  1342.  
  1343.         index = index - Offset
  1344.  
  1345.         IF index < 0 THEN 'too far down the table?
  1346.  
  1347.             'wrap back the index to the end of the table
  1348.  
  1349.             index = index + Table.Size
  1350.  
  1351.         END IF
  1352.  
  1353.     END IF
  1354.  
  1355. LOOP
  1356.  
  1357. END SUB
  1358.  
  1359.  
  1360.  
  1361. SUB pal (c, R, G, B)
  1362.  
  1363. OUT &H3C8, c
  1364.  
  1365. OUT &H3C9, R
  1366.  
  1367. OUT &H3C9, G
  1368.  
  1369. OUT &H3C9, B
  1370.  
  1371. END SUB
  1372.  
  1373.  
  1374.  
  1375. 'Puts a byte into the GIF file & also takes care of each block.
  1376.  
  1377. SUB PutByte (a) STATIC
  1378.  
  1379. BlockLength = BlockLength - 1 'are we at the end of a block?
  1380.  
  1381. IF BlockLength <= 0 THEN ' yup,
  1382.  
  1383.     BlockLength = 255 'block length is now 255
  1384.  
  1385.     LastLoc& = LOC(1) + 1 + (OAddress - OStartAddress) 'remember the pos.
  1386.  
  1387.     BufferWrite 255 'for later fixing
  1388.  
  1389. END IF
  1390.  
  1391. BufferWrite a 'put a byte into the buffer
  1392.  
  1393. END SUB
  1394.  
  1395.  
  1396.  
  1397. 'Puts an LZW variable-bit code into the output file...
  1398.  
  1399. SUB PutCode (a) STATIC
  1400.  
  1401. Char& = Char& + a * Shift(CurrentBit) 'put the char were it belongs;
  1402.  
  1403. CurrentBit = CurrentBit + CodeSize ' shifting it to its proper place
  1404.  
  1405. DO WHILE CurrentBit > 7 'do we have a least one full byte?
  1406.  
  1407.     PutByte Char& AND 255 ' yup! mask it off and write it out
  1408.  
  1409.     Char& = Char& \ 256 'shift the bit buffer right 8 bits
  1410.  
  1411.     CurrentBit = CurrentBit - 8 'now we have 8 less bits
  1412.  
  1413. LOOP 'until we don't have a full byte
  1414.  
  1415. END SUB
  1416.  
  1417.  
  1418. DECLARE SUB gifload (A$)
  1419.  
  1420. SUB gifload (filein$, wide%, high%)
  1421. DEFINT A-Z
  1422. CONST GifIdStr$ = "GIF87a"
  1423. CONST BitsInByte% = 8
  1424. CONST GifMaxEncodeBits% = 12
  1425. CONST GifBitsMag% = 4095 '* 2^GifMaxEncodeBits%-1 (aka 12 bits) CONST does not handle operations by another prior defined CONST :(
  1426. GifInChannel% = FREEFILE
  1427. IF GifInChannel% > 0 THEN
  1428.     DIM Prefix(0 TO GifBitsMag%)
  1429.     DIM Suffix(0 TO GifBitsMag%)
  1430.     DIM OutStack(0 TO GifBitsMag%)
  1431.     DIM shiftout%(8)
  1432.     DIM Ybase AS LONG, powersof2(0 TO GifMaxEncodeBits% - 1) AS LONG, WorkCode AS LONG
  1433.  
  1434.     FOR A% = 0 TO BitsInByte% - 1
  1435.         shiftout%(BitsInByte% - A%) = 2 ^ A%
  1436.     NEXT A%
  1437.     FOR A% = 0 TO GifMaxEncodeBits% - 1
  1438.         powersof2(A%) = 2 ^ A%
  1439.     NEXT A%
  1440.     IF filein$ = "" THEN
  1441.         INPUT "GIF file"; filein$
  1442.         IF filein$ = "" THEN END
  1443.     END IF
  1444.     IF INSTR(filein$, ".") = 0 THEN filein$ = filein$ + ".gif"
  1445.     OPEN filein$ FOR BINARY AS #GifInChannel%
  1446.     A$ = SPACE$(LEN(GifIdStr$))
  1447.     GET #GifInChannel%, , A$
  1448.     IF A$ <> GifIdStr$ THEN
  1449.         PRINT "Not a "; GifIdStr$; " file."
  1450.         EXIT SUB
  1451.     END IF
  1452.     GET #GifInChannel%, , TotalX: GET #GifInChannel%, , TotalY
  1453.     GOSUB GetByte
  1454.     NumColors = 2 ^ ((A% AND 7) + 1)
  1455.     NoPalette = (A% AND 128) = 0
  1456.     GOSUB GetByte
  1457.     Background = A%
  1458.     GOSUB GetByte
  1459.     IF A% <> 0 THEN
  1460.         PRINT "Bad screen descriptor."
  1461.         EXIT SUB
  1462.     END IF
  1463.     IF NoPalette = 0 THEN
  1464.         P$ = SPACE$(NumColors * 3)
  1465.         GET #GifInChannel%, , P$
  1466.     END IF
  1467.     DO
  1468.         GOSUB GetByte
  1469.         IF A% = 44 THEN
  1470.             EXIT DO
  1471.         ELSEIF A% <> 33 THEN
  1472.             PRINT "Unknown extension type.": END
  1473.         END IF
  1474.         GOSUB GetByte
  1475.         DO
  1476.             GOSUB GetByte
  1477.             A$ = SPACE$(A%)
  1478.             GET #GifInChannel%, , A$
  1479.         LOOP UNTIL A% = 0
  1480.     LOOP
  1481.     GET #GifInChannel%, , XStart
  1482.     GET #GifInChannel%, , YStart
  1483.     GET #GifInChannel%, , XLength
  1484.     GET #GifInChannel%, , YLength
  1485.     XEnd = XStart + XLength
  1486.     YEnd = YStart + YLength
  1487.     GOSUB GetByte
  1488.     IF A% AND 128 THEN PRINT "Can't handle local colormaps.": END
  1489.     Interlaced = A% AND 64
  1490.     PassNumber = 0
  1491.     PassStep = 8
  1492.     GOSUB GetByte
  1493.     ClearCode = 2 ^ A%
  1494.     EOSCode = ClearCode + 1
  1495.     FirstCode = ClearCode + 2
  1496.     NextCode = FirstCode
  1497.     StartCodeSize = A% + 1
  1498.     CodeSize = StartCodeSize
  1499.     StartMaxCode = 2 ^ (A% + 1) - 1
  1500.     MaxCode = StartMaxCode
  1501.  
  1502.     BitsIn = 0
  1503.     BlockSize = 0
  1504.     BlockPointer = 1
  1505.     x% = XStart
  1506.     y% = YStart
  1507.     Ybase = y% * wide%
  1508.  
  1509.     SCREEN 13: DEF SEG = &HA000
  1510.     IF NoPalette = 0 THEN
  1511.         OUT &H3C7, 0
  1512.         OUT &H3C8, 0
  1513.         FOR A% = 1 TO NumColors * 3
  1514.             OUT &H3C9, ASC(MID$(P$, A%, 1)) \ 4
  1515.         NEXT A%
  1516.     END IF
  1517.     LINE (0, 0)-(wide% - 1, high% - 1), Background, BF
  1518.     DO
  1519.         GOSUB GetCode
  1520.         IF Code <> EOSCode THEN
  1521.             IF Code = ClearCode THEN
  1522.                 NextCode = FirstCode
  1523.                 CodeSize = StartCodeSize
  1524.                 MaxCode = StartMaxCode
  1525.                 GOSUB GetCode
  1526.                 CurCode = Code
  1527.                 LastCode = Code
  1528.                 LastPixel = Code
  1529.                 IF x% < wide% THEN POKE x% + Ybase, LastPixel
  1530.                 x% = x% + 1
  1531.                 IF x% = XEnd THEN GOSUB NextScanLine
  1532.             ELSE
  1533.                 CurCode = Code
  1534.                 StackPointer = 0
  1535.                 IF Code > NextCode THEN EXIT DO
  1536.                 IF Code = NextCode THEN
  1537.                     CurCode = LastCode
  1538.                     OutStack(StackPointer) = LastPixel
  1539.                     StackPointer = StackPointer + 1
  1540.                 END IF
  1541.  
  1542.                 DO WHILE CurCode >= FirstCode
  1543.                     OutStack(StackPointer) = Suffix(CurCode)
  1544.                     StackPointer = StackPointer + 1
  1545.                     CurCode = Prefix(CurCode)
  1546.                 LOOP
  1547.  
  1548.                 LastPixel = CurCode
  1549.                 IF x% < wide% THEN POKE x% + Ybase, LastPixel
  1550.                 x% = x% + 1
  1551.                 IF x% = XEnd THEN GOSUB NextScanLine
  1552.  
  1553.                 FOR A% = StackPointer - 1 TO 0 STEP -1
  1554.                     IF x% < wide% THEN POKE x% + Ybase, OutStack(A%)
  1555.                     x% = x% + 1
  1556.                     IF x% = XEnd THEN GOSUB NextScanLine
  1557.                 NEXT A%
  1558.  
  1559.                 IF NextCode <= GifBitsMag% THEN
  1560.                     Prefix(NextCode) = LastCode
  1561.                     Suffix(NextCode) = LastPixel
  1562.                     NextCode = NextCode + 1
  1563.                     IF NextCode > MaxCode AND CodeSize < GifMaxEncodeBits% THEN
  1564.                         CodeSize = CodeSize + 1
  1565.                         MaxCode = MaxCode * 2 + 1
  1566.                     END IF
  1567.                 END IF
  1568.                 LastCode = Code
  1569.             END IF
  1570.         END IF
  1571.     LOOP UNTIL DoneFlag OR Code = EOSCode
  1572.     CLOSE #GifInChannel%
  1573.     ERASE Prefix
  1574.     ERASE Suffix
  1575.     ERASE OutStack
  1576.     ERASE shiftout%
  1577.     ERASE powersof2
  1578. END IF
  1579. EXIT SUB
  1580.  
  1581. GetByte:
  1582. A$ = " "
  1583. GET #GifInChannel%, , A$
  1584. A% = ASC(A$)
  1585. RETURN
  1586.  
  1587. NextScanLine:
  1588. IF Interlaced THEN
  1589.     y% = y% + PassStep
  1590.     IF y% >= YEnd THEN
  1591.         PassNumber = PassNumber + 1
  1592.         SELECT CASE PassNumber
  1593.             CASE 1
  1594.                 y% = 4
  1595.                 PassStep = 8
  1596.             CASE 2
  1597.                 y% = 2
  1598.                 PassStep = 4
  1599.             CASE 3
  1600.                 y% = 1
  1601.                 PassStep = 2
  1602.         END SELECT
  1603.     END IF
  1604. ELSE
  1605.     y% = y% + 1
  1606. END IF
  1607. x% = XStart
  1608. Ybase = y% * wide%
  1609. DoneFlag = y% > high%
  1610. RETURN
  1611.  
  1612. GetCode:
  1613. IF BitsIn = 0 THEN
  1614.     GOSUB ReadBufferedByte
  1615.     LastChar = A%
  1616.     BitsIn = 8
  1617. END IF
  1618. WorkCode = LastChar \ shiftout%(BitsIn)
  1619. DO WHILE CodeSize > BitsIn
  1620.     GOSUB ReadBufferedByte
  1621.     LastChar = A%
  1622.     WorkCode = WorkCode OR LastChar * powersof2(BitsIn)
  1623.     BitsIn = BitsIn + 8
  1624. LOOP
  1625. BitsIn = BitsIn - CodeSize
  1626. Code = WorkCode AND MaxCode
  1627. RETURN
  1628.  
  1629. ReadBufferedByte:
  1630. IF BlockPointer > BlockSize THEN
  1631.     GOSUB GetByte: BlockSize = A%
  1632.     A$ = SPACE$(BlockSize)
  1633.     GET #GifInChannel%, , A$
  1634.     BlockPointer = 1
  1635. END IF
  1636. A% = ASC(MID$(A$, BlockPointer, 1))
  1637. BlockPointer = BlockPointer + 1
  1638. RETURN
  1639. END SUB

Notice, this also has a routine to load GIF files as well as save them in it.  Not something I needed for my personal use (neither is the ability to save less than 256 colors, which I also removed), since we have _LOADIMAGE to do the work for us, but it's there if anyone ever wants to just study from it for learning purposes.

(Also be aware, there are a few glitches in there, such as the LOC(1) using a hard handle of 1, when it should be the filehandle instead.  If you're not saving to file #1, your image is going to end up quite corrupt, unless you correct the problem.)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: SaveGIF
« Reply #2 on: August 14, 2019, 11:13:57 am »
Wait. What? Save as GIF89a? You mean the GIF format that supports microanimation? I tried. The header of the file has the identity GIF87a (still image). It's great to have this option. Are you planning to expand it also to 89a format (probably in the form of an image array in input)?


Perfect work, Steve!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: SaveGIF
« Reply #3 on: August 14, 2019, 12:06:31 pm »
Wait. What? Save as GIF89a? You mean the GIF format that supports microanimation? I tried. The header of the file has the identity GIF87a (still image). It's great to have this option. Are you planning to expand it also to 89a format (probably in the form of an image array in input)?


Perfect work, Steve!

I’ve been studying up on GIF format, in an attempt to sort out how they do the animation.  As time allows, I’d love to be able to add that ability into the SaveImage Library, but I haven’t sorted out all the details yet — but, after all, I’ve only been digging into the routines/format for about 2 days now.  Give me enough time, and I’ll sort something out with it eventually.  ;)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!