home *** CD-ROM | disk | FTP | other *** search
/ RUN Flagazine: Run 16 / unpacked-run16.zip / JPH.BAS < prev    next >
BASIC Source File  |  1995-01-01  |  14KB  |  519 lines

  1. '
  2. ' JPH.BAS - Jarno Peschier, (c) RUN Flagazine (Huffman file compressor)
  3. '
  4.  
  5. %No = 0
  6. %Yes = NOT %No
  7.  
  8. $LIB ALL OFF
  9. $OPTIMIZE SPEED
  10. $STRING 32
  11. $SOUND 0
  12. $COM 0
  13. $EVENT OFF
  14. $OPTION CNTLBREAK ON
  15.  
  16. '=== INIT ==================================================================
  17.  
  18. TYPE HuffNode        'Hoe ziet een knoop van de Huffman boom eruit?
  19.   Char AS STRING * 1    'Het teken
  20.   Freq AS DWORD        'De frequentie
  21.   Up   AS WORD        'Parent pointer
  22.   Zero AS WORD        'Left pointer
  23.   One  AS WORD        'Right pointer
  24. END TYPE
  25.  
  26. TYPE FileHeader        'Hoe ziet de header van een JPH file eruit?
  27.   ID    AS STRING * 17    'ID string
  28.   Ver    AS BYTE        'JPH versie (alleen nog 1)
  29.   File    AS STRING * 13    'Oorspronkelijke naam ingepakte file
  30.   Size    AS DWORD    'Oorspronkelijke grootte ingepakte file
  31.   Nodes AS WORD        'Aantal knopen Huffman boom
  32.   Root    AS WORD        'Wortel van Huffman boom
  33.   Res    AS STRING * 25    'Gereserveerde ruimte (header = 64 bytes)
  34. END TYPE
  35.  
  36. %Top = 1024        'Aangeven dat knoop een wortel is
  37. %MaxFiles = 1024    'Maximaal aantal files met wildcards
  38.  
  39. ' De Huffman boom is een array van nodes (met onderlinge pointers)
  40. DIM Huff(511) AS SHARED HuffNode
  41. DIM NoOfNodes AS SHARED WORD
  42. DIM Root AS SHARED WORD
  43.  
  44. ' Opzoektabel met Huffman codes om coderen/decoderen te versnellen
  45. DIM Code(255) AS SHARED STRING
  46.  
  47. ' Buffers bij voor input/output (I/O)
  48. DIM IOBuf AS SHARED STRING
  49. DIM BitBuffer AS SHARED STRING
  50.  
  51. ' Schermbuffer voor meldingen in windowtjes
  52. DIM BeforeMsg AS SHARED STRING
  53.  
  54. ' Roep het programma zelf aan
  55. MainProgram
  56. END
  57.  
  58. '=== MAIN ==================================================================
  59.  
  60. SUB MainProgram
  61.   DIM File(1024) AS LOCAL STRING
  62.   DIM NoOfFiles AS LOCAL WORD
  63.   DIM FileName AS LOCAL STRING
  64.   DIM Path AS LOCAL STRING
  65.   DIM I AS LOCAL INTEGER
  66.   ' Laat zien wie we zijn
  67.   PRINT
  68.   PRINT"JPH 0.02, Copyright 13 augustus 1995, Jarno Peschier, RUN Flagazine 16"
  69.   PRINT
  70.   ' Geef helpscherm als helemaal geen parameters zijn opgegeven
  71.   IF Command$ = "" THEN
  72.     PRINT"Syntax: JPH [filespec]"
  73.     PRINT
  74.     PRINT"Als de opgegeven file een JPH gecomprimeerde file is dan"
  75.     PRINT"zal die worden uitgepakt naar de originele naam."
  76.     PRINT
  77.     PRINT"Als de opgegeven file geen JPH gecomprimeerde file is dan"
  78.     PRINT"zal die worden ingepakt tot een JPH door gebruik te maken"
  79.     PRINT"van simpele Huffman compressie, zoals uitgelegd is in"
  80.     PRINT"het programma over compressie op RUN Flagazine 16."
  81.     PRINT
  82.     PRINT"Als je een file met weinig redundatie erin probeert te"
  83.     PRINT"comprimeren, is de kans erg groot dat het resultaat nog"
  84.     PRINT"groter wordt dan het origineel. In dat geval heeft JPH"
  85.     PRINT"compressie geen enkel nut. Hetzelfde geldt voor het com-"
  86.     PRINT"primeren van een kleine file, waarvan de Huffman boom die"
  87.     PRINT"nodig is al groter is dan de hele file."
  88.   ELSE
  89.     ' Bepaal alle files die voldoen aan de opgegeven file specificatie
  90.     NoOfFiles = 0
  91.     FileName = DIR$(COMMAND$)
  92.     IF FileName <> "" THEN
  93.       Path = UCASE$(COMMAND$)
  94.       FOR I = LEN(Path) TO 1 STEP -1
  95.         IF MID$(Path,I,1) = "\" THEN
  96.           Path = LEFT$(Path,I)
  97.           EXIT FOR
  98.         END IF
  99.       NEXT I
  100.       IF TALLY(Path,"\") = 0 AND MID$(Path,2) <> ":" THEN Path = ""
  101.       DO
  102.         INCR NoOfFiles
  103.         File(NoOfFiles) = FileName
  104.         FileName = DIR$
  105.       LOOP WHILE FileName <> "" AND NoOfFiles < %MaxFiles
  106.       ' Handel iedere gevonden filenaam af
  107.       FOR I = 1 TO NoOfFiles
  108.         Handle Path+File(I)
  109.       NEXT I
  110.     ELSE
  111.       PRINT"Bestand(en) niet gevonden"
  112.     END IF
  113.   END IF
  114. END SUB
  115.  
  116. SUB Handle(BYVAL File AS STRING)    'Behandel een file
  117.   IF IsJPH(File) THEN
  118.     DeCompress File
  119.   ELSE
  120.     Compress File
  121.   END IF
  122. END SUB
  123.  
  124. SUB Compress(File AS STRING)        'Pak een file in tot een JPH file
  125.   DIM Buffer AS LOCAL STRING
  126.   DIM I AS LOCAL INTEGER
  127.   PRINT File;
  128.   BuildHuffmanTree File
  129.   BuildCodeTable
  130.   OPEN File FOR BINARY AS #2
  131.   PRINT " --> ";StartWritingJPH(File);
  132.   Message "File gecomprimeerd wegschrijven..."
  133.   WHILE NOT EOF(2)
  134.     GET$ #2,32000,Buffer
  135.     FOR I = 1 TO LEN(Buffer)
  136.       WriteBits Code(ASCII(MID$(Buffer,I,1)))
  137.     NEXT I
  138.   WEND
  139.   RestoreScreen
  140.   PRINT" ("+LTRIM$(STR$(LOF(1)*100\LOF(2)))+"% over)"
  141.   StopWritingJPH
  142.   CLOSE #2
  143. END SUB
  144.  
  145. SUB DeCompress(File AS STRING)        'Pak een JPH file weer uit
  146.   DIM Result AS LOCAL STRING
  147.   DIM OriginalSize AS LOCAL DWORD
  148.   DIM ToWrite AS LOCAL STRING
  149.   Result = File
  150.   OriginalSize = StartReadingJPH(Result)
  151.   BuildDecodeTable
  152.   WHILE DIR$(Result) <> ""
  153.     BEEP
  154.     PRINT Result+" bestaat al, geef een nieuwe naam: ";
  155.     INPUT "",Result
  156.     Result = UCASE$(Result)
  157.   WEND
  158.   PRINT File;" ==> ";Result
  159.   Message "File uitpakken..."
  160.   OPEN Result FOR BINARY AS #2
  161.   WHILE NOT EndOfJPH
  162.     BitBuffer = BitBuffer + ReadBits
  163.     ToWrite = Lookup(BitBuffer)
  164.     PUT$ #2, ToWrite
  165.   WEND
  166.   CLOSE #2
  167.   RestoreScreen
  168.   StopReadingJPH
  169. END SUB
  170.  
  171. '=== HUFFMAN BOOM BOUWEN ====================================================
  172.  
  173. SUB BuildHuffmanTree(File AS STRING)    'Bouw een optimale Huffman boom
  174.   DIM Freq(255) AS LOCAL DWORD        'voor de opgegeven file
  175.   DIM Buffer AS LOCAL STRING
  176.   DIM I AS LOCAL WORD
  177.   DIM J AS LOCAL WORD
  178.   DIM Node AS LOCAL HuffNode
  179.   DIM BeforeMsg AS LOCAL STRING
  180.   ' Frequentietabel op nul zetten
  181.   FOR I = 0 TO 255
  182.     Freq(I) = 0
  183.   NEXT I
  184.   ' Tekenfrequenties bepalen
  185.   Message "Tekenfrequenties bepalen..."
  186.   OPEN File FOR BINARY AS #1
  187.   WHILE NOT EOF(1)
  188.   GET$ #1, 32000, Buffer
  189.     FOR I = 1 TO LEN(Buffer)
  190.       INCR Freq(ASCII(MID$(Buffer,I,1)))
  191.       IF Freq(ASCII(MID$(Buffer,I,1))) > 32000 THEN
  192.       END IF
  193.     NEXT I
  194.   WEND
  195.   CLOSE #1
  196.   RestoreScreen
  197.   ' Losse knopen in de Huffman boom zetten
  198.   Message "Optimale Huffman boom bouwen..."
  199.   NoOfNodes = 0
  200.   FOR I = 0 TO 255
  201.     IF Freq(I) > 0 THEN
  202.       INCR NoOfNodes
  203.       Node.Char = CHR$(I)
  204.       Node.Freq = Freq(I)
  205.       Node.Up = %Top
  206.       Node.Zero = 0
  207.       Node.One = 0
  208.       Huff(NoOfNodes) = Node
  209.     END IF
  210.   NEXT I
  211.   SortNodes
  212.   ' Samenstellen knopen tot een optimale Huffman boom
  213.   DO
  214.     I = NextTopNode
  215.     J = NextTopNode
  216.     INCR NoOfNodes
  217.     Node.Char = CHR$(0)
  218.     Node.Freq = Huff(I).Freq + Huff(J).Freq
  219.     Node.Up = %Top
  220.     Node.Zero = I
  221.     Node.One = J
  222.     Huff(NoOfNodes) = Node
  223.     SortNodes
  224.   LOOP WHILE TopNodes > 1
  225.   ' Parent pointers bepalen uit child pointers
  226.   FOR I = 1 TO NoOfNodes
  227.     IF Huff(I).Zero <> 0 THEN
  228.       Huff(Huff(I).Zero).Up = I
  229.     END IF
  230.     IF Huff(I).One <> 0 THEN
  231.       Huff(Huff(I).One).Up = I
  232.     END IF
  233.   NEXT I
  234.   ' Wortel bepalen en opslaan
  235.   Root = NextTopNode
  236.   ' Huffman boom is klaar voor gebruik
  237.   RestoreScreen
  238. END SUB
  239.  
  240. SUB SortNodes
  241.   DIM I AS LOCAL INTEGER
  242.   DIM J AS LOCAL INTEGER
  243.   DIM Temp AS LOCAL HuffNode
  244.   ' Sorteer alle knopen op oplopende frequentie (insertion sort)
  245.   FOR I = 2 TO NoOfNodes
  246.     Temp = Huff(I)
  247.     J = I - 1
  248.     WHILE J > 0 AND Huff(J).Freq > Temp.Freq
  249.       Huff(J+1) = Huff(J)
  250.       DECR J
  251.     WEND
  252.     Huff(J+1) = Temp
  253.   NEXT I
  254. END SUB
  255.  
  256. FUNCTION NextTopNode AS INTEGER
  257.   DIM I AS LOCAL INTEGER
  258.   ' Zoek eerstvolgende knoop die een wortel is
  259.   FOR I = 1 TO NoOfNodes
  260.     IF Huff(I).Up = %Top THEN
  261.       Huff(I).Up = 0
  262.       NextTopNode = I
  263.       EXIT FUNCTION
  264.     END IF
  265.   NEXT I
  266. END FUNCTION
  267.  
  268. FUNCTION TopNodes AS INTEGER
  269.   DIM N AS LOCAL INTEGER
  270.   ' Tel het aantal wortels dat over is
  271.   N = 0
  272.   FOR I = 1 TO NoOfNodes
  273.     IF Huff(I).Up = %Top THEN INCR N
  274.   NEXT I
  275.   TopNodes = N
  276. END FUNCTION
  277.  
  278. '=== GEEF TEKEN BIJ HUFFMAN CODE EN VICE VERSA =============================
  279.  
  280. SUB BuildCodeTable
  281.   DIM I AS LOCAL WORD
  282.   FOR I = 1 TO NoOfNodes
  283.     IF Huff(I).Zero + Huff(I).One = 0 THEN
  284.       Code(ASCII(Huff(I).Char)) = HuffCode(ASCII(Huff(I).Char))
  285.     END IF
  286.   NEXT I
  287. END SUB
  288.  
  289. FUNCTION HuffCode(Teken AS INTEGER) AS STRING
  290.   DIM I AS LOCAL WORD
  291.   DIM Node AS LOCAL WORD
  292.   DIM Code AS LOCAL STRING
  293.   FOR I = 1 TO NoOfNodes
  294.     IF Huff(I).Char = CHR$(Teken) THEN
  295.       Node = I
  296.       EXIT FOR
  297.     END IF
  298.   NEXT I
  299.   Code = ""
  300.   DO
  301.     IF Huff(Huff(Node).Up).Zero = Node THEN
  302.       Code = "0" + Code
  303.     ELSE
  304.       Code = "1" + Code
  305.     END IF
  306.     Node = Huff(Node).Up
  307.   LOOP WHILE Huff(Node).Up > 0
  308.   HuffCode = Code
  309. END FUNCTION
  310.  
  311. SUB BuildDecodeTable
  312.   DIM I AS LOCAL WORD
  313.   FOR I = 1 TO NoOfNodes
  314.     IF Huff(I).Zero + Huff(I).One = 0 THEN
  315.       Code(ASCII(Huff(I).Char)) = HuffCode(ASCII(Huff(I).Char))
  316.     END IF
  317.   NEXT I
  318. END SUB
  319.  
  320. FUNCTION Lookup(BitString AS STRING) AS STRING
  321.   DIM Node AS LOCAL WORD
  322.   DIM Result AS LOCAL STRING
  323.   DIM Code AS LOCAL STRING
  324.   Node = Root
  325.   Result = ""
  326.   Code = BitString
  327.   ' Afdalen in de boom en decoderen tot Code leeg is
  328.   WHILE Code <> ""
  329.     SELECT CASE LEFT$(Code,1)
  330.       CASE "0": Node = Huff(Node).Zero
  331.       CASE "1": Node = Huff(Node).One
  332.       CASE ELSE: BEEP:END
  333.     END SELECT
  334.     Code = MID$(Code,2)
  335.     IF Huff(Node).Zero + Huff(Node).One = 0 THEN
  336.       Result = Result + Huff(Node).Char
  337.       Node = Root
  338.     END IF
  339.   WEND
  340.   ' Als we ergens middenin de boom eindigen, ga dan terug omhoog in de
  341.   ' boom om de "rest" te vinden zodat je die in BitString kan laten.
  342.   IF Node = Root THEN
  343.     BitString = ""
  344.   ELSE
  345.     DO
  346.       IF Huff(Huff(Node).Up).Zero = Node OR Node = Root THEN
  347.         Code = "0" + Code
  348.       ELSE
  349.         Code = "1" + Code
  350.       END IF
  351.       Node = Huff(Node).Up
  352.     LOOP WHILE Huff(Node).Up > 0
  353.     BitString = Code
  354.   END IF
  355.   Lookup = Result
  356. END FUNCTION
  357.  
  358. '=== DISK I/O ==============================================================
  359.  
  360. ' General
  361.  
  362. FUNCTION IsJPH(File AS STRING) AS INTEGER    'Is de file een JPH file?
  363.   DIM Header AS LOCAL FileHeader
  364.   DIM Result AS LOCAL INTEGER
  365.   IF DIR$(File) <> "" THEN Result = %Yes
  366.   IF INSTR(File, ANY "*?") > 0 THEN Result = %No
  367.   IF Result = %Yes THEN
  368.     OPEN File FOR BINARY AS #1
  369.       GET #1,,Header
  370.       IF Header.ID <> "JPH compressed"+CHR$(10,13,26) THEN Result = %No
  371.       IF Header.Ver <> 1 THEN Result = %No
  372.     CLOSE #1
  373.   END IF
  374.   IsJPH = Result
  375. END FUNCTION
  376.  
  377. ' Output
  378.  
  379. FUNCTION StartWritingJPH(File AS STRING) AS STRING    'Begin JPH file
  380.   DIM Header AS LOCAL FileHeader
  381.   DIM I AS LOCAL WORD
  382.   OPEN File FOR BINARY AS #1
  383.   IF MID$(File,2,1) = ":" THEN File = MID$(File,3)
  384.   WHILE INSTR(File,"\") > 0
  385.     File = MID$(File,INSTR(File,"\")+1)
  386.   WEND
  387.   Header.ID    = "JPH compressed"+CHR$(10,13,26)
  388.   Header.Ver   = 1
  389.   Header.File  = File
  390.   Header.Size  = LOF(1)
  391.   Header.Nodes = NoOfNodes
  392.   Header.Root  = Root
  393.   Header.Res   = STRING$(LEN(Header.Res),0)
  394.   CLOSE #1
  395.   File = File + "."
  396.   File = LEFT$(File,INSTR(File,"."))+"JPH"
  397.   OPEN File FOR BINARY AS #1
  398.   PUT #1,,Header
  399.   FOR I = 1 TO NoOfNodes
  400.     PUT #1,,Huff(I)
  401.   NEXT I
  402.   IOBuf = ""
  403.   BitBuffer = ""
  404.   StartWritingJPH = File
  405. END FUNCTION
  406.  
  407. SUB WriteBits(BitString AS STRING)        'Schrijf bits naar JPH file
  408.   BitBuffer = BitBuffer + BitString
  409.   WHILE LEN(BitBuffer) > 7
  410.     IOBuf = IOBuf + CHR$(VAL("&B"+LEFT$(BitBuffer,8)))
  411.     BitBuffer = MID$(BitBuffer,9)
  412.   WEND
  413.   IF LEN(IOBuf) => 64 THEN
  414.     PUT$ #1,IOBuf
  415.     IOBuf = ""
  416.   END IF
  417. END SUB
  418.  
  419. SUB StopWritingJPH            'Stop met schrijven (buffer legen)
  420.   BitBuffer = BitBuffer + STRING$(8-(LEN(BitBuffer) MOD 8),"0")
  421.   WHILE BitBuffer <> ""
  422.     IOBuf = IOBuf + CHR$(VAL("&B"+LEFT$(BitBuffer,8)))
  423.     BitBuffer = MID$(BitBuffer,9)
  424.   WEND
  425.   PUT$ #1, IOBuf
  426.   CLOSE #1
  427. END SUB
  428.  
  429. ' Input
  430.  
  431. FUNCTION StartReadingJPH(File AS STRING) AS DWORD    'Begin lezen JPH
  432.   DIM Header AS LOCAL FileHeader
  433.   DIM I AS LOCAL WORD
  434.   IF IsJPH(File) THEN
  435.     OPEN File FOR BINARY AS #1
  436.     GET #1,,Header
  437.     File = RTRIM$(Header.File)
  438.     StartReadingJPH = Header.Size
  439.     NoOfNodes = Header.Nodes
  440.     Root = Header.Root
  441.     FOR I = 1 TO NoOfNodes
  442.       GET #1,,Huff(I)
  443.     NEXT I
  444.     IOBuf = ""
  445.     BitBuffer = ""
  446.   ELSE
  447.     PRINT"Geen JPG file..."
  448.     END 1
  449.   END IF
  450. END FUNCTION
  451.  
  452. FUNCTION EndOfJPH AS INTEGER        'Is het eind van de file bereikt?
  453.   EndOfJPH = EOF(1)
  454. END FUNCTION
  455.  
  456. FUNCTION ReadBits AS STRING        'Lees een blok van bits (2K maximum)
  457.   DIM I AS LOCAL INTEGER
  458.   GET$ #1,2024,IOBuf
  459.   FOR I = 1 TO LEN(IOBuf)
  460.     BitBuffer = BitBuffer+RIGHT$("00000000"+BIN$(ASCII(MID$(IOBuf,I,1))),8)
  461.   NEXT I
  462.   ReadBits = BitBuffer
  463. END FUNCTION
  464.  
  465. SUB StopReadingJPH                              'Stop met lezen
  466.   CLOSE #1
  467. END SUB
  468.  
  469. '=== SCREEN I/O ============================================================
  470.  
  471. SUB Message(Msg AS STRING)
  472.   DIM OldSeg AS LOCAL WORD
  473.   DIM I AS LOCAL INTEGER
  474.   DIM Y AS LOCAL INTEGER
  475.   DIM X AS LOCAL INTEGER
  476.   BeforeMsg = SaveScreen
  477.   OldSeg = pbvDefSeg
  478.   IF (pbvScrnCard AND 64) THEN
  479.     DEF SEG = &HB000
  480.   ELSE
  481.     DEF SEG = &HB800
  482.   END IF
  483.   I = 40-LEN(Msg)\2
  484.   FOR Y = 10 TO 12
  485.     POKE$ Y*160+2*(I-2),REPEAT$(LEN(Msg)+4,CHR$(&H20,&H1F))
  486.   NEXT Y
  487.   Y = 11
  488.   FOR X = I TO I+LEN(Msg)-1
  489.     POKE Y*160+2*X,ASCII(MID$(Msg,X-I+1,1))
  490.   NEXT X
  491.   DEF SEG = OldSeg
  492. END SUB
  493.  
  494. FUNCTION SaveScreen AS STRING
  495.   DIM OldSeg AS LOCAL WORD
  496.   OldSeg = pbvDefSeg
  497.   IF (pbvScrnCard AND 64) THEN
  498.     DEF SEG = &HB000
  499.   ELSE
  500.     DEF SEG = &HB800
  501.   END IF
  502.   SaveScreen = CHR$(CSRLIN)+CHR$(POS(0))+PEEK$(0,4000)
  503.   DEF SEG = OldSeg
  504. END FUNCTION
  505.  
  506. SUB RestoreScreen
  507.   DIM OldSeg AS LOCAL WORD
  508.   OldSeg = pbvDefSeg
  509.   IF (pbvScrnCard AND 64) THEN
  510.     DEF SEG = &HB000
  511.   ELSE
  512.     DEF SEG = &HB800
  513.   END IF
  514.   POKE$ 0, MID$(BeforeMsg,3,4000)
  515.   DEF SEG = OldSeg
  516.   LOCATE ASCII(LEFT$(BeforeMsg,1)),ASCII(MID$(BeforeMsg,2,1))
  517. END SUB
  518.  
  519. '===========================================================================