; ppm2AGA utility functions written by Günther Röhrich

; the version for 68020+ processors is slightly faster and smaller
; the function is called from C:
; EncodeHAM(char *yorig, char *yham, char *ColorTable,
;            short NumColors, short xsize);
; yorig       = original row in rgbrgb... format
; yham        = row in HAM chunky format
; ColorTable  = color table in brgbrg... format
; NumColors   = (number of valid colors in ColorTable)*3
; xsize       = size of row in pixels
; ConvertMode should contain: 0 for HAM6 encoding
;                             1 for HAM8 encoding

; int MapColor(colorhist_vector colormap, pixval r1, pixval g1, pixval b1,
;              int NumColors) 
; see later


	IFD MC68020
 	 MACHINE MC68020
	ENDC

	IFD MC68000
 	 MACHINE MC68000
	ENDC


;NOTE: GCC stores all data as 32 bit at subroutine calls!

yorig        EQU 44  ;pointer to actual row (original)
yham         EQU 48  ;pointer to actual row (HAM)
ColorTable   EQU 52  ;address of color table (3 bytes per entry)
 IFD GCC
NumColors    EQU 58
xsize        EQU 62
 ELSE
NumColors    EQU 56  ;number of colors allocated so far (*3)
xsize        EQU 58  ;number of pixels (x)
 ENDC

colormap   EQU 44  ;pointer to colorhist_vector
 IFD GCC
r1         EQU 51
g1         EQU 55
b1         EQU 59
NColors    EQU 60
 ELSE
r1         EQU 48  ;red component (byte format)
g1         EQU 50  ;green component
b1         EQU 52  ;blue component
NColors    EQU 54  ;number of colors in colormap
 ENDC

    
        XREF _Mult_Table  ;address of multiplication table
        XREF _Mult_Table32
        XREF _ConvertMode        ;HAM8 or HAM6
        XDEF _EncodeHAM  ;entry point for function
        XDEF _MapColorASM
        XREF _ColorCache ;an 256K array

*       XDEF _blue_left ;only for debugging purposes
*       XDEF _search_finish ;only for debugging
        XDEF _MaxError
        XDEF _MaxErrorPos 
        XDEF _hit       ;only for debugging
        XDEF _t1        ;only for debugging        

               dseg

 cnop 0,2
_blue_left:      dc.b 0  ;this order is better for HAM-encoding
_red_left:       dc.b 0
_green_left:	 dc.b 0,0 ;additional dummy-value for faster longword access

 cnop 0,2
_offset_orig:   dc.w 0
_offset_ham:    dc.w 0
_MaxError:      dc.w 0
_MaxErrorPos    dc.w 0
_CacheOffset    dc.l 0
       
		cseg

;register usage:   D0 = general purpose register
;                  D1 = contains afterwards the error
;                  D2 = orig_blue
;                  D3 = orig_red
;                  D4 = orig_green
;                  D5 = color that should be set
;                  D6 = offset to actual pixel (HAM)
;                  D7 = offset for color table / (0,1,2) at HAM
;                  A0 = best color so far (color+1)*3
;                  A1 = pointer to multiplication table
;                  A2 = pointer to color table / to _blue_left
;                  A3 = smallest error that has been reached so far
;                  A4 = used by the Aztec assembler (small data model)
;                  A5 = pointer to the actual row (original)
;                  A6 = pointer to the actual row (HAM)

;WARNING: This will not work with color values higher than 63
;(HAM8 has a maximum of 63, HAM6 has a maximum of 15)
;(values higher than 63 may result in memory corruption)

;computing the difference of color values is done with signed 8 bit
;arithmetic 

;the maximum error value is 63^2+63^2+63^2=11907
;the summation has to be done therefore with 16 bits


;Initializing
_EncodeHAM:     movem.l D2-D7/A2-A3/A5/A6,-(A7)  ;store registers
                lea     _blue_left,A0      ;load start of left colors
                move.l  ColorTable(sp),A2
                IFD MC68020
                  move.l  (A2),(A0)
                ELSE
                  move.b  (A2)+,(A0)+      ;initialize left colors
                  move.b  (A2)+,(A0)+      ;A2 may not be word aligned
                  move.b  (A2)+,(A0)+
                  lea     -3(A0),A0        ;correct register
                  lea     -3(A2),A2        ;correct register
                ENDC
                moveq.l #0,D6              ;initialize ham offset
                move.l  D6,_MaxError       ;initialize absolute max error
                                           ;and max error pos      
                move.l  yham(sp),A6                           
                move.l  yorig(sp),A5
                lea     _Mult_Table,A1
                lea     255*2(A1),A1              
search_begin:   move.b  (A5)+,D3           ;load registers with original colors
                move.b  (A5)+,D4           ;for faster access
                move.b  (A5)+,D2
		IFD GCC
                 lea     1(A5),A5          ;adjust A5 for correct alignment
                ENDC                       ;needed for GNU C
                move.w  #15000,A3          ;dummy-value for minimum error so far
                move.l  ColorTable(sp),A2
                moveq.l #0,D7              ;initialize offset for color table

;First take a look if we have the value already in the cache
		moveq.l  #0,D0
                moveq.l  #0,D1             
                move.b   D3,D0             ;compute the cache offset
                lsl.l    #6,D0
                or.b     D4,D0
                lsl.l    #6,D0
                or.b     D2,D0             ;now we have the offset in D0
                movea.l  _ColorCache,A0    ;load start address of the cache
                move.b   0(A0,D0.l),D1     ;take the value from the cache
                bne     _hit               ;jump if we have a cache hit
                
                move.l   D0,_CacheOffset   ;store the offset to avoid recomputing it
                move.l   #3,A0             ;dummy-value for best colornumber so far
                                           ; (3 means color 0)

search_start:	bsr      _compute_error
                cmp.w    D1,A3              ;A3 <= D1 ?
                bls.s    search4
                move.w   D1,A3              ;D1 is smaller than A3, store it
                move.w   D7,A0              ;store color number     
                tst.w    D1                 ;do we have the correct color ?
                beq      search5            ;then finish immediately
search4:        cmp.w    NumColors(sp),D7   ;have we reached highest colornum ?
                bne.s    search_start       ;no, then once again                   
                                        
;A0 contains now (colornumber+1)*3
;A3 contains the error for that colornumber

_t1:            move.w   A0,D0        
                movea.l  _ColorCache,A2
                move.l   _CacheOffset,D1
                move.b   D0,0(A2,D1.l)      ;store the value in the cache                
                lea      _blue_left,A2      ;restore A2

;compute the error when using modify mode

start_ham6:     move.b   D2,D0              ;load orig_blue
                moveq.l  #0,D7              ;assume blue should be changed
                move.b   D2,D5              ;store value to change
                sub.b    _blue_left,D0      ;D0=D0-_blue_left
                bpl.s    ham6_1
                neg.b    D0                 ;make result positive
ham6_1:         move.b   D0,D1              ;store maximum error so far
                move.b   D3,D0              ;load orig_red
                sub.b    _red_left,D0       ;D0=D0-_red_left
                bpl.s    ham6_2
                neg.b    D0              
ham6_2:         cmp.b    D0,D1              ;check D1-D0
                bge.s    ham6_3             ;jump if D1>=D0
                move.b   D0,D1              ;store new maximum error
                moveq.l  #1,D7              ;assume red should be changed
                move.b   D3,D5              ;store value to change
ham6_3:         move.b   D4,D0              ;load orig_green
                sub.b    _green_left,D0     ;D0=D0-_green_left
                bpl.s    ham6_4
                neg.b    D0
ham6_4:         cmp.b    D0,D1
                bge.s    ham6_5
                move.b   D0,D1              ;store maximum error
                moveq.l  #2,D7              ;green should be changed
                move.b   D4,D5              ;store value to change
ham6_5:         lea      _blue_left,A2      
                move.b   D5,0(A2,D7.W)      ;perform change

ham_error:      moveq.l  #0,D1
                moveq.l  #0,D0
                move.b   D2,D0              ;load blue_origin
                sub.b    (A2)+,D0           ;D0 = blue_color - blue_origin
                ext.w    D0
                IFD MC68020
                  add.w    0(A1,D0.W*2),D1
                ELSE
                  add.w    D0,D0            
                  add.w    0(A1,D0.W),D1    ;D1 = D1 + D0*D0
                ENDC
                move.b   D3,D0              ;load red_origin
                sub.b    (A2)+,D0           ;D0 = red_color - red_origin 
                ext.w    D0
                IFD MC68020
                  add.w    0(A1,D0.W*2),D1
                ELSE
                  add.w    D0,D0
                  add.w    0(A1,D0.W),D1
                ENDC
                move.b   D4,D0              ;load green_origin
                sub.b    (A2)+,D0           ;D0 = green_color - green_origin
                ext.w    D0
                IFD MC68020
                  add.w    0(A1,D0.W*2),D1
                ELSE
                  add.w    D0,D0
                  add.w    0(A1,D0.W),D1    ;D1 contains error from HAM encoding
                ENDC

                cmpa.w   D1,A3              ;check what error is smaller
                bls.s    _search_finish     ;jump if colortable is better
                cmp.w    _MaxError,D1       ;compare with absolute max error
                ble.s    ham6_9             ;jump if _MaxError is greater
                cmp.w    #2,D6              ;less then 2 pixels from left ?
                bpl.s    ham6_8a
                lsr.w    #1,D1              ;half error
ham6_8a:        move.w   D1,_MaxError       
                move.w   D6,_MaxErrorPos
ham6_9:         add.b    #1,D7              ;needed to get correct code
                tst.w    _ConvertMode       ;ConvertMode = 0  -> HAM6
                beq.s    ham6_10            ;ConvertMode != 0 -> HAM8
                IFD MC68020
                  lsl.b    #2,D7            ;HAM8-adjust
                ELSE
                  add.b    D7,D7            ;this is faster than shifting
                  add.b    D7,D7            ;on the 68000
                ENDC
ham6_10:        lsl.b    #4,D7              ;HAM6-adjust                      
                or.b     D5,D7
                move.b   D7,(A6,D6.W)       ;store code in bitmap

                addq.w   #1,D6              ;increase _offset_ham
                cmp.w    xsize(sp),D6       ;end of column ?
                bne      search_begin
                bra.s    search_end

_search_finish: move.w   A3,D0              ;move error to D0
                cmp.w    #2,D6              ;less then 2 pixels from left ?
                bpl.s    _search_finish1
                lsr.w    #1,D0              ;half error
_search_finish1: cmp.w    _MaxError,D0      ;compare with absolute max error
                ble.s    _search_finish2
                move.w   D0,_MaxError
                move.w   D6,_MaxErrorPos
_search_finish2: move.w   A0,D0
                move.l   ColorTable(sp),A2
                lea      -3(A2,D0.W),A3     ;load A3 with pointer to colors
                divu     #3,D0
                subq.w   #1,D0
                move.b   D0,0(A6,D6.W)      ;store colornumber in bitmap
                lea      _blue_left,A2
                IFD MC68020
                  move.l   (A3),(A2)
                ELSE
                  move.b   (A3)+,(A2)+      ;store new left colors
                  move.b   (A3)+,(A2)+      ;A3 may not be word aligned
                  move.b   (A3)+,(A2)+      ;
                ENDC
                addq.w   #1,D6              ;increase _offset_ham
                cmp.w    xsize(sp),D6       ;have we reached the end ?
                bne      search_begin

search_end:     movem.l  (A7)+,D2-D7/A2-A3/A5/A6  ;restore registers
                rts                         ;jump back to caller
                


;we jump here if we have reached error 0 by colortable only
;and there was not a cache hit
search5:        move.w   A0,D0         
                movea.l  _ColorCache,A2
                move.l   _CacheOffset,D1
                move.b   D0,0(A2,D1.l)      ;store the value in the cache                
                bra.s    _search_finish


;we jump here if we have a cache hit
;D1 contains the best color number, but we have to compute the error

_hit:           subq.w   #3,D1              ;correct color number
                move.w   D1,D7
                bsr.s    _compute_error
		move.w   D1,A3              ;D1 is smaller than A3, store it
                move.w   D7,A0              ;store color number     
                bra      start_ham6         ;continue with HAM encoding


;compute the error


_compute_error: moveq.l  #0,D1
                moveq.l  #0,D0             
                move.b   D2,D0              ;load blue_origin
                sub.b    0(A2,D7.W),D0      ;D0 = blue_color - blue_origin
                ext.w    D0                 ;extend result to word
                IFD MC68020
                  add.w    0(A1,D0.W*2),D1
                ELSE
                  add.w    D0,D0            
                  add.w    0(A1,D0.W),D1    ;D1 = D1 + D0*D0
                ENDC
                addq.w   #1,D7              ;advance color table offset
                move.b   D3,D0              ;load red_origin
                sub.b    0(A2,D7.W),D0      ;D0 = red_color - red_origin
                ext.w    D0
                IFD MC68020
                  add.w    0(A1,D0.W*2),D1
                ELSE
                  add.w    D0,D0
                  add.w    0(A1,D0.W),D1
                ENDC
                addq.w   #1,D7              ;advance color table offset
                move.b   D4,D0              ;load green_origin
                sub.b    0(A2,D7.W),D0      ;D0 = green_color - green_origin
                ext.w    D0
                IFD MC68020
                  add.w    0(A1,D0.W*2),D1  ;D1 = D1 + D0*D0
                ELSE
                  add.w    D0,D0
                  add.w    0(A1,D0.W),D1
                ENDC
                addq.w   #1,D7              ;advance color table offset
		rts

                




;now comes the brute-force colormap search code
;if you know how to make it faster/smaller please let me know

;the 68020 main loop is only 56 bytes long, it should 
;fit completely into the 256 bytes instruction cache of the 68020
;or 68030 processor

;NOTE: this is a bit different compared to the HAM-code because
;signed 8 bit math gives wrong results with values higher than 127
;it computes the differences therefore with 16 bit

;the maximum error value is 3*255^2=195075, we will therefore need
;32 bit arithmetic for the summation

;register usage:
;                 D0 = general purpose register, return value
;                 D1 = contains error
;                 D2 = r1
;                 D3 = g1
;                 D4 = b1
;                 D5 = holds color from colormap
;                 D6 = counter
;                 D7 = color table offset
;                 A1 = pointer to multiplication table
;                 A2 = pointer to colormap
;                 A3 = smallest error
;                 A5 = actual error
;                 A6 = NColors

_MapColorASM:   movem.l D2-D7/A2-A3/A5/A6,-(A7)  ;store registers
                move.l  colormap(sp),A2
                move.l  NColors(sp),A6
                lea     _Mult_Table32,A1
                lea     255*4(A1),A1
                moveq.l #0,D2               ;ensure that the higher bytes
                moveq.l #0,D3               ;of these registers are cleared
                moveq.l #0,D4  
                move.b  r1(sp),D2
                move.b  g1(sp),D3
                move.b  b1(sp),D4
                move.l  #2000000000,A3      ;dummy value for min. error 
                moveq.l #0,D6               ;initialize counter
                moveq.l #0,D7
                moveq.l #0,D5

;now comes the main loop

Map_start:      suba.l   A5,A5              ;clear A5 (actual error)
                move.l   D2,D0              ;load r1
                move.b   0(A2,D7.W),D5      ;load colormap.red
                sub.w    D5,D0              ;D0 = colormap.red - r1
                IFD MC68020
                  add.l    0(A1,D0.W*4),A5
                ELSE
                  lsl.w    #2,D0            
                  add.l    0(A1,D0.W),A5    ;A5 = A5 + D0*D0
                ENDC
                move.l   D3,D0              ;load g1
                move.b   1(A2,D7.W),D5      ;load colormap.green
                sub.w    D5,D0              ;D5 = colormap.green - g1
                IFD MC68020
                  add.l    0(A1,D0.W*4),A5
                ELSE
                  lsl.w    #2,D0
                  add.l    0(A1,D0.W),A5
                ENDC
                move.l   D4,D0              ;load b1
                move.b   2(A2,D7.W),D5      ;load colormap.blue
                sub.w    D5,D0              ;D0 = colormap.blue - b1
                IFD MC68020               
                  add.l    0(A1,D0.W*4),A5  ;A5 = A5 + D0*D0
                ELSE
                  lsl.w    #2,D0
                  add.l    0(A1,D0.W),A5
                ENDC
                addq.w   #8,D7              ;advance color table offset              
              
                cmp.l    A5,A3              ;A3 - A5 ?
                bls.s    Map1
                move.l   A5,A3              ;D1 is smaller than A3, store it
                move.w   D6,A0              ;store color number     
                move.l   A5,D0              ;do we have the correct color ?
                beq.s    Map_finish         ;then finish immediately
Map1:           addq.w   #1,D6              ;advance color number
                cmp.l    A6,D6              ;have we reached highest colornum ?
                bne.s    Map_start          ;no, then once again                                               
Map_finish:     move.l   A0,D0              ;store return value
                movem.l  (A7)+,D2-D7/A2-A3/A5/A6  ;restore registers
                rts                         ;jump back to caller
end

