'===========================================================================
' Subject: LOAD 16 COLOR PCX                  Date: Unknown Date           
'  Author: Greg Turgeon                       Code: PB                     
'  Origin: comp.lang.basic.misc             Packet: EGAVGA.ABC
'===========================================================================
'                    Contains LoadPCX16, a 16 color PCX image file
'                        loader for VGA graphics mode 12h
'
'This file contains the following routines:
'
'SUB LoadPCX16(pcx$, Sline%, Col%)    16 color PCX file loader
'SUB LoadColorPCX16                   loads 16 color PCX data to VGA DAC regs
'
'IMPORTANT: LoadPCX16 calls the routine VideoOff to blank the screen
'while the PCX image is being loaded.  If you disable this feature,
'be sure to rem out both calls to VideoOff.
'
'ALSO IMPORTANT: LoadPCX16 allows an image smaller than full-screen
'(640 X 480) to be repositioned on screen.  However, instead of the
'coordinate system (X,Y), with X and Y identifying pixel positions,
'LoadPCX16 employs (Sline%, Col%) with Sline% identifying a vertical
'screen pixel position and Col% following the text mode convention
'(in this case 0-79) for horizontal positioning.  The routine performs
'error-checking for available repositioning space on-screen.
'
'As now written, the demo requires VGA.  LoadPCX16 calls the routine
'LoadColorPCX16, which employs BIOS calls available only on color VGA
'hardware.  Error checking for repositioning also assumes VGA mode 12h
'(640 X 480) only.
'
'I first wrote LoadPCX16 in BASIC (PB 3.0c).  On my 386SX 16, loading the
'test file (a fairly complex image originally generated by FRACTINT)
'from a RAM drive took over 90 seconds.  The routine as presented here,
'converted almost entirely to assembly, loads the same image in 1.8 seconds.
'
'My thanks to Murray Moffatt for his patience and persistence while
'testing LoadPCX16.
'
'Greg Turgeon - CIS: 76470,2417

$LIB GRAPH ON
$LIB VGA ON
DECLARE FUNCTION GetStrLoc&( BYVAL AllocHandle% ) 'must be declared

DEFINT A - Z
%yes = -1: %no = 0

'''create variable to load w/PCX file header data
TYPE PCXheader
  Mfg           AS BYTE
  Version       AS BYTE
  Encoding      AS BYTE
  BitsPerPixel  AS BYTE
  Xmin          AS INTEGER
  Ymin          AS INTEGER
  Xmax          AS INTEGER
  Ymax          AS INTEGER
  HorizontalRes AS INTEGER
  VerticalRes   AS INTEGER
  Pal           AS STRING * 48
  Reserved      AS BYTE
  NumColrPlanes AS BYTE
  BytesPerSLine AS INTEGER
  PalInfo       AS INTEGER
  Filler        AS STRING * 58
END TYPE
DIM PIX1 AS SHARED PCXheader

'''use command$ to identify pix to load
pcx$ = UCASE$( COMMAND$ )
IF ISFALSE( LEN( DIR$( pcx$ ))) THEN
  PRINT: PRINT "Cannot find PCX file "; pcx$
  END
END IF

CALL LoadPCX16( pcx$, Sline%, Col% )
END

'===========================
SUB LoadPCX16( FileName$, BYVAL Sline%, BYVAL Col% )
  PCXfile = FREEFILE
  OPEN FileName$ FOR BINARY AS PCXfile
  DOShandle% = FILEATTR( PCXfile, 2 ) 'DOS handle needed for asm
  FileBytes& = LOF( PCXfile )

'''load header into var & verify that PCX file is correct format
  get# PCXfile,, PIX1
  IF PIX1.Mfg < > 10 OR PIX1.Version < > 5 THEN 'Mfg 10 = ZSoft, Version 5 = 3.
    CLOSE PCXfile
    PRINT: PRINT "mfg: "; PIX1.mfg, "Version"; PIX1.Version
    PRINT "Incorrect PCX version"
    EXIT SUB
  END IF

  PixWidth% = PIX1.Xmax - PIX1.Xmin
  PixHeight% = PIX1.Ymax - PIX1.Ymin

  PRINT
  PRINT "Width: "; PixWidth%, "Height:"; PixHeight%
  PRINT "Encoding type:"; PIX1.Encoding
  PRINT "Bits per pixel per plane:"; PIX1.BitsPerPixel
  PRINT "Horizontal resolution of originating system:"; PIX1.HorizontalRes;
  PRINT "  Vertical resolution:"; PIX1.VerticalRes
  PRINT "Number of color planes:"; PIX1.NumColrPlanes
  PRINT "Number of bytes per scan line per plane:"; PIX1.BytesPerSLine
  PRINT "Palette info (color/bw = 1, grayscale = 2):"; PIX1.PalInfo
  PRINT "File size: "; FileBytes&; " bytes"

  SLEEP
  SCREEN 12

'''error checking: don't reposition image unless there's room
  MaxX% = 639: MaxY% = 479
  IF Sline% > ( MaxY% - PixHeight% ) - 2 THEN Sline% = 0
  IF Col% > (( MaxX% - PixWidth% ) \ 8 ) THEN Col% = 0

  PixBPerLine% = PIX1.BytesPerSLine 'create for asm
  CALL LoadColorPCX16 'load PIX1.Pal colors
  SEEK PCXfile, 128 'start of screen data

  ChunkSize% = FRE( t$ ) 'create largest buffer possible
  FileBuffer$ = STRING$( ChunkSize%, 0 ) '(reduce size to smooth out display if

  ! push WORD ptr FileBuffer$
  ! CALL getstrloc; now dx: ax = LOC, cx = length
  ! mov FBytesSeg??, dx; SAVE SEG & addr of FileBuffer$
  ! mov FBytesPtr??, ax
'''establish offset if repositioning image
  ! mov ax, Sline%
  ! mov dx, 80
  ! mul dx
  ! add ax, Col%
  ! mov Mover??, ax
'''determine how many bytes per line for the current video mode
'''bytes per line will = screen column figure in BIOS data area
  ! xor bx, bx
  ! mov es, bx
  ! mov bx, &h44A
  ! mov ax, es: [bx]
  ! mov BPerLine%, ax
  ! CALL LoadChunk        ; load FileBuffer$
  ! mov ScreenLine%, -1   ; start AT - 1 TO allow FOR inc TO 0
'''begin loading pix to screen
NewLine:
  ! inc ScreenLine%
  ! mov dx, ScreenLine%
  ! cmp dx, PixHeight%    ; IF ScreenLine% > PixHeight%, THEN PixDone
  ! jle LineOK
  ! jmp PixDone
LineOK:
  ! mov ax, BPerLine%     ; Addr?? = BPerLine% * ScreenLine%
  ! imul dx
  ! mov di, ax            ; di = target SCREEN address FOR loading
  ! add ax, PixBPerLine%  ; LineEnd?? = Addr?? + PixBPerLine%( PIX1.BytesPerSLi
  ! mov LineEnd??, ax
'''si = ptr to position in FileBuffer$, Plane% = target video plane
  ! mov Plane%, 0         ; begin each LINE w / plane 0
  ! CALL SelectPlane
NewByte:
  ! cmp Plane%, 3         ; done WITH ALL 3 planes?
  ! ja NewLine            ; IF yes
  ! CALL GetNextByte      ; IF no, load a BYTE into al FROM FileBuffer$
  ! mov ah, al            ; make a copy of NextByte?
  ! AND al, 192           ; IF top 2 BITS NOT set, THEN load the one BYTE
  ! cmp al, 192           ; IF set, THEN it 's a repeater, so load the
  ! je RepByte            ; bytes AND assume continuing ON same LINE
  ! mov al, ah            ; RESTORE al = NextByte?, AND load BYTE
  ! push di               ; SAVE di( stosb increases di )
  ! mov dx, &h0A000       ; BASE video SEG
  ! add di, Mover??       ; add ANY repositioning value
  ! stosb                 ; load the BYTE TO SCREEN
  ! pop di
  ! inc di                ; update position FOR loading

  ! mov ax, LineEnd??     ; check: AT the END of a SCREEN line?
  ! cmp ax, di
  ! ja NewByte            ; IF no
  ! mov ax, ScreenLine%   ; IF yes, THEN move back TO
  ! mov bx, BPerLine%     ;
  ! imul bx               ; start of LINE AND switch
  ! mov di, ax            ;
  ! inc Plane%            ; TO NEXT video plane
  ! CALL SelectPlane
  ! jmp NewByte
RepByte:
'''coming in, ah = NextByte?
  ! mov al, ah            ; RESTORE al = NextByte?
  ! AND al, 63            ; CLEAR BITS 6&7 TO leave the
  ! mov cl, al            ; number of times TO REPEAT
  ! xor ch, ch
  ! CALL GetNextByte      ; load the COLOR BYTE into al
DoTheReps:
  ! push di
  ! mov dx, &h0A000
  ! mov es, dx            ; di already = address
  ! add di, Mover??       ; add ANY repositioning value
  ! stosb; load TO video
  ! pop di

  ! inc di
  ! cmp di, LineEnd??     ; AT END of line?
  ! je NextPlane          ; IF yes, GOSUB NextPlane
DoNextRep:
  ! LOOP DoTheReps        ; IF no
  ! jmp NewByte
NextPlane:
  ! push ax
  ! push dx
  ! mov ax, ScreenLine%   ; move back TO start of LINE
  ! mov dx, BPerLine%     ; AND
  ! imul dx               ; move TO NEXT video plane
  ! mov di, ax
  ! inc Plane%
  ! pop dx
  ! pop ax
  ! CALL SelectPlane
  ! jmp DoNextRep
PixDone:
'''reset all planes
  ! mov ax, &h0F02
  ! mov dx, &h3C4
  ! OUT dx, ax

  CLOSE PCXfile

  SLEEP

  SCREEN 0
  EXIT SUB
GetNextByte:
'''don't push ax; it sends back NextByte?
  ! push bx
  ! push cx
  ! push es

  ! mov es, FBytesSeg??
  ! mov bx, FBytesPtr??
  ! add bx, si; si = FileBuffer$ BYTE ptr, so bx now - > NextByte?
  ! mov al, BYTE ptr es: [bx]; now al = NextByte?

  ! inc si; increase FileBuffer$ ptr
  ! dec BuffPtr%; decrease ptr FOR countdown
  ! jnz ChunkNotDone; IF more IN FileBuffer$
  ! CALL LoadChunk; IF empty, THEN GET more
ChunkNotDone:
  ! pop es
  ! pop cx
  ! pop bx
  ! retn
LoadChunk:
  ! push ax
  ! push bx
  ! push cx
  ! push dx
  ! push ds
'if FileBytes& =< ChunkSize% then ChunkSize% = FileBytes&
  ! mov ax, FileBytes&[00]
  ! mov dx, FileBytes&[02]
  ! cmp dx, 0            ; IF dx < > 0 THEN FileBytes& must
  ! jg SameSize          ; be > ChunkSize%
  ! cmp ax, ChunkSize%
  ! jle SameSize         ; IF FileBytes& < ChunkSize%, THEN make
  ! mov ChunkSize%, ax   ; ChunkSize% = FileBytes& FOR final pass
SameSize:
  ! mov bx, FBytesSeg??
  ! mov ds, bx
  ! mov dx, FBytesPtr??
  ! mov bx, DOShandle%
  ! mov cx, ChunkSize%
  ! mov ah, &h3F; reload FileBuffer$
  ! INT &h21
  ! jnc ReCalc
ErrorHandler:
  ! mov ChunkSize%, ax
  ! pop ds
  ! pop dx
  ! pop cx
  ! pop bx
  ! pop ax
  CLOSE
  LOCATE 1, 1
  IF ISTRUE( ChunkSize% ) THEN
    SOUND 800, .5: PRINT "Error: "; ChunkSize%
  END IF
  getkey
  SCREEN 0
  END
ReCalc:
  ! mov ax, FileBytes&[00]  ; recalculate size of remaining FileBytes&
  ! mov dx, FileBytes&[02]
  ! mov bx, ChunkSize%      ; subtract portion already loaded TO SCREEN
  ! SUB ax, bx
  ! sbb dx, 0
  ! mov FileBytes&[02], dx
  ! mov FileBytes&[00], ax
  ! xor si, si              ; si = FileBuffer$ ptr FOR loading; start AT 0
  ! mov ax, ChunkSize%
  ! mov BuffPtr%, ax        ; ptr FOR countdown

  ! pop ds
  ! pop dx
  ! pop cx
  ! pop bx
  ! pop ax
  ! retn
SelectPlane:
  ! push ax
  ! push bx
  ! push cx
  ! push dx

  ! mov ax, 1; determine 2 ^ plane
  ! cbw
  ! mov cx, Plane%
  ! shl ax, cl
  ! mov ah, al; ah now = plane desired
  ! mov dx, &h3C4; plane SELECT
  ! mov al, 2
  ! OUT dx, ax

  ! pop dx
  ! pop cx
  ! pop bx
  ! pop ax
  ! retn
END SUB

'===========================
SUB LoadColorPCX16
  NumBytes?? = LEN( PIX1.Pal )
  Addr1?? = VARPTR( PIX1.Pal )
'''palette regs actually index -> DAC regs
'''build array of the DAC regs to which palette regs (0-15) are indexed
  REDIM temp?( 0: 15 )
  RESTORE DefaultDACregs
  FOR a? = 0 TO 15: READ temp?( a? ): NEXT a?
    DACValSeg?? = VARSEG( temp?( 0 )): DACValPtr?? = VARPTR( temp?( 0 ))
'''reduce PCX 0-255 color values to 0-63
    ! push ax
    ! push bx
    ! push cx
    ! push dx
    ! push es
    ! push si
    ! push di; make both ds: si & es: di - > PIX1.Pal

    ! mov ax, ds; ALL fixed length strings are IN ds
    ! mov es, ax
    ! mov ax, Addr1??
    ! mov si, ax
    ! mov di, ax
    ! mov cx, NumBytes??
    ! cld; increment
Reducer:
    ! lodsb
    ! shr al, 1; \ 4 TO reduce
    ! shr al, 1
    ! stosb
    ! LOOP Reducer
'''load each DAC reg
    ! mov si, Addr1??; now ds: [si] = PIX1.Pal
    ! mov es, DACValSeg??
    ! mov di, DACValPtr??; es: [di] = temp%( 0 )
    ! mov cx, 16
    ! mov ax, &h1010
    ! xor bx, bx
LoadRegs:
    ! push cx
    ! mov bl, BYTE ptr es: [di]; pal REG
    ! mov dh, BYTE ptr ds: [si]; red
    ! inc si
    ! mov ch, BYTE ptr ds: [si]; green
    ! inc si
    ! mov cl, BYTE ptr ds: [si]; blue
    ! inc si

    ! push bp
    ! INT &h10
    ! pop bp

    ! pop cx
    ! inc di; NEXT pal REG
    ! LOOP LoadRegs

    ! pop di
    ! pop si
    ! pop es
    ! pop dx
    ! pop cx
    ! pop bx
    ! pop ax
    ERASE temp?
    EXIT SUB
DefaultDACregs:
    DATA 0, 1, 2, 3, 4, 5, 20, 7, 56, 57, 58, 59, 60, 61, 62, 63
END SUB
