From: RWilson@acorn.co.uk
Newsgroups: comp.sys.acorn
Subject: How to find the closest colour out of the RISC OS 256
Date: 30 Jan 92 12:20:23 GMT
; MACRO ColErr
;
; Calculates an error value in $error corresponding to the difference
; between the two colours $col1 and $col2. Requires two temporary
; registers. All registers must be different. This routine requires
; 16 instructions, three of which are multiples plus for instructions
; for the error loading calculations. To speed up the multiplies
; the error value for each component is made positive.
;
; The error calculation is controlled by the following ``loading'' values.
; The basic component error calculation, given components c1 and c2 from
; $col1 and $col2, is:-
;
; e := ((c1-c2) ^ 2) * load
;
; Thus a higher ``load'' makes errors in the component more significant when
; the component value itself is small, however has little effect when the
; component value is large. Each loading must not exceed 65536/3 or the
; error calculation may overflow. If a loading exceeds 32768/3 the result may
; have the top bit set. The loadings are determined algorithmically by the
; following macros - this avoids having to do a real multiplication. The
; macros have the form:-
;
; XWeight $err, $sqr
;
; and calculate:-
;
; $err = $sqr * XLoad (for BLoad)
; $err += $sqr * XLoad (for GLoad, RLoad)
;
; $sqr may be overwritten. They are called in the order BLoad, GLoad
; RLoad, and are only called if the corresponding loading value is not
; 1.
;
BLoad EQU 1
MACRO
$label BWeight $error, $sqr
ASSERT 0
MEND
GLoad EQU 10
MACRO
$label GWeight $error, $sqr
$label ADD $sqr, $sqr, $sqr, LSL #2
ADD $error, $error, $sqr, LSL #1
MEND
RLoad EQU 3
MACRO
$label RWeight $error, $sqr
$label ADD $sqr, $sqr, $sqr, LSL #1
ADD $error, $error, $sqr
MEND
MACRO
$label ColErr $error, $col1, $col2, $temp1, $temp2
$label MOV $temp1, $col1, LSR #24
SUBS $temp2, $temp1, $col2, LSR #24
RSBLT $temp2, $temp2, #0
[ BLoad /= 1
MUL $temp1, $temp2, $temp2
BWeight $error, $temp1
|
MUL $error, $temp2, $temp2
]
MOV $temp2, #255
AND $temp1, $temp2, $col1, LSR #16
AND $temp2, $temp2, $col2, LSR #16
SUBS $temp2, $temp1, $temp2
RSBLT $temp2, $temp2, #0
[ GLoad /= 1
MUL $temp1, $temp2, $temp2
GWeight $error, $temp1
|
MLA $error, $temp2, $temp2, $error
]
MOV $temp2, #255
AND $temp1, $temp2, $col1, LSR #8
AND $temp2, $temp2, $col2, LSR #8
SUBS $temp2, $temp1, $temp2
RSBLT $temp2, $temp2, #0
[ RLoad /= 1
MUL $temp1, $temp2, $temp2
RWeight $error, $temp1
|
MLA $error, $temp2, $temp2, $error
]
MEND
;
; MACRO CompErr
;
; This macro also calculates an error value, however the second colour
; is specified as three separate r, g, b values. The registers containing
; these values can be the same, if desired. The registers should not be
; the same as any of the other registers. The calculation needs 16
; instructions, including three multiplies.
;
MACRO
$label CompErr $error, $col, $red, $green, $blue, $temp1, $temp2
$label SUBS $temp2, $blue, $col, LSR #24
RSBLT $temp2, $temp2, #0
[ BLoad /= 1
MUL $temp1, $temp2, $temp2
BWeight $error, $temp1
|
MUL $error, $temp2, $temp2
]
AND $temp1, $col, #&FF0000 ; green component, still shifted
SUBS $temp2, $green, $temp1, LSR #16
RSBLT $temp2, $temp2, #0 ; |green error|
[ GLoad /= 1
MUL $temp1, $temp2, $temp2
GWeight $error, $temp1
|
MLA $error, $temp2, $temp2, $error
]
AND $temp1, $col, #&FF00 ; red component, still shifted
SUBS $temp2, $red, $temp1, LSR #8
RSBLT $temp2, $temp2, #0 ; |red error|
[ RLoad /= 1
MUL $temp1, $temp2, $temp2
RWeight $error, $temp1
|
MLA $error, $temp2, $temp2, $error
]
MEND
;
; MACRO FindCol
;
; This macro finds the closest colour to the given r, g, b triple from
; an array of (word sized) RGB values (encoded BBGGRR00). The macro
; preserves the $red, $green and $blue values, exits with $error set
; to that of the found colour and $index set to the index of the entry. Again,
; all arguments must be different registers.
;
MACRO
$label FindCol $list, $listend, $red, $green, $blue, $error, $index, $ptr, $col, $temp1, $temp2, $temp3
$label MOV $error, #&FFFFFFFF ; maximum error
SUB $ptr, $list, #4 ; points to entry to last entry tried
0: LDR $col, [$ptr, #4]!
CompErr $temp3, $col, $red, $green, $blue, $temp1, $temp2
CMP $temp3, $error
MOVLS $error, $temp3
SUBLS $index, $list, $ptr ; index * 4
CMP $ptr, $listend
BLO 0b
MOV $index, $index, LSR #2
MEND
;
; MACRO Find256
;
; This macro finds the best matching colour for the *standard* ARM 256 entry palette - the
; one with the R/G/B/T (tint) bits. The algorithm returns a palette value encoded in the
; standard way (BGGRBRTT) in $col and the error in $error.
; All arguments must be different registers. The loop is about 44 instructions, including
; the normal three multiplies. The code goes round it four times, there is a further 12
; instruction overhead.
;
; The registers must all be different except for $red, $green and $blue, which can be
; the same if desired.
;
; The ARM palette entries are assumed to expand a 4 bit component to an 8 bit component
; using c<<4|c - this has been determined experimentally to give good results.
;
MACRO
$label Find256 $red, $green, $blue, $error, $col, $tint, $temp1, $temp2, $temp3, $pixel
$label MOV $error, #&FFFFFFFF
MOV $tint, #&30:SHL:23 ; tint bits unexpanded
0: RSB $temp1, $tint, #&20:SHL:23 ; overflow is not possible here
SUB $temp2, $blue, $blue, LSR #4 ; effectively multiplication by 16/17
ADDS $temp1, $temp1, $temp2, LSL #23
;
; At this point the top bits of $temp1 hold the best blue bit values given
; the current $tint tint bits, however the desired value may be >11tt or <00tt,
; in either case the top bit (bit 31) of $temp1 will be set, hence the N flag
; will be set in the PSR. We must distinguish overflow (>11tt) from a simple
; negative result (<00tt) and truncate both to the appropriate end of the
; scale. We have calculated (blue-tint+&22)<<23. The overflow (V) flag will
; ONLY be set for >11tt; the other possible results (in the range &FF<<23 to
; -&17<<23 are representable without overflow), so:-
;
MOVVSS $temp1, #&7F000000 ; clears the N flag!
MOVMI $temp1, #0
;
; Now extract the blue bits and reconstruct the real (expanded) blue value.
;
AND $temp1, $temp1, #&60000000 ; two blue bits
ADD $temp1, $temp1, $tint ; plus tint
ADD $pixel, $temp1, $temp1, LSR #4 ; expand component bits - 8 bit blue value
;
; Calculate the error as above.
;
SUBS $temp2, $blue, $pixel, LSR #23
RSBLT $temp2, $temp2, #0 ; speeds up multiplication
[ BLoad /= 1
MUL $temp1, $temp2, $temp2
BWeight $temp3, $temp1
|
MUL $temp3, $temp2, $temp2
]
;
; Repeat this for the green component, accumulating the error
;
RSB $temp1, $tint, #&20:SHL:23
SUB $temp2, $green, $green, LSR #4
ADDS $temp1, $temp1, $temp2, LSL #23
MOVVSS $temp1, #&7F000000
MOVMI $temp1, #0
;
AND $temp1, $temp1, #&60000000 ; two green bits
ADD $temp1, $tint, $temp1 ; 4 bit green value
ADD $temp1, $temp1, $temp1, LSR #4 ; expand component bits
ORR $pixel, $pixel, $temp1, LSR #8 ; Accumulate bits in $pixel
;
SUBS $temp2, $green, $temp1, LSR #23
RSBLT $temp2, $temp2, #0
[ GLoad /= 1
MUL $temp1, $temp2, $temp2
GWeight $temp3, $temp1
|
MLA $temp3, $temp2, $temp2, $temp3
]
;
; And the red component:-
;
RSB $temp1, $tint, #&20:SHL:23
SUB $temp2, $red, $red, LSR #4
ADDS $temp1, $temp1, $temp2, LSL #23
MOVVSS $temp1, #&7F000000
MOVMI $temp1, #0
;
AND $temp1, $temp1, #&60000000 ; two red bits
ADD $temp1, $tint, $temp1 ; 4 bit red value
ADD $temp1, $temp1, $temp1, LSR #4 ; expand component bits
ORR $pixel, $pixel, $temp1, LSR #16 ; Accumulate bits in $pixel
;
SUBS $temp2, $red, $temp1, LSR #23
RSBLT $temp2, $temp2, #0
[ RLoad /= 1
MUL $temp1, $temp2, $temp2
RWeight $temp3, $temp1
|
MLA $temp3, $temp2, $temp2, $temp3
]
;
; $temp3 contains the error for the ARM value in $pixel (actually this value
; is shifted right by 1 bit because of the LSL 23 above). Check the error
; and see if this is a better pixel.
;
CMP $temp3, $error
MOVLS $error, $temp3 ; LS, so selects lower tint in preference
MOVLS $col, $pixel ; $col holds best match
;
; Try the next tint
;
SUBS $tint, $tint, #&10:SHL:23
BGE 0b
;
; $error is the error, and is directly comparable with the $error
; value from the other macros. $col to the ARM palette entry using
; the appropriate bit manipulations. The value is currently:-
;
; 0BBTT011 1GGTT011 1RRTT011 10000000
;
; We need to convert this to the form:-
;
; 76543210
; BGGRBRTT
;
AND $temp1, $col, #&40000000 ; B (needs >> 23)
MOV $temp2, $temp1, LSR #23
AND $temp1, $col, #&600000 ; GG (needs >> 16)
ORR $temp2, $temp2, $temp1, LSR #16
AND $temp1, $col, #&4000 ; R (needs >> 10)
ORR $temp2, $temp2, $temp1, LSR #10
AND $temp1, $col, #&20000000 ; B (needs >> 26)
ORR $temp2, $temp2, $temp1, LSR #26
AND $temp1, $col, #&3800 ; RTT (needs >> 11)
ORR $col, $temp2, $temp1, LSR #11
MEND