02-21-2024, 03:34 PM
(02-20-2024, 07:45 PM)RhoSigma Wrote: Uhh Terry, I really like the style you're commenting the routines. That was seriously a lot of work to construct all these boxes and walls and keep them aligned while writing/changing the code, but it makes the code very readable.Thank you, another interesting piece of code to examine. Using an image buffer as an array is something I want to investigate further. Thanks for the idea!
Mayby you also wanna examine my Floyd-Steinberg remapper function. I made this for my GuiTools Framework and also tried to make it as fast as possible by using _MEM where possible, I even use a small 2px high 32-bit image and misuse it as simple LONG array to sum up pixal values.
Code: (Select All)'---------------------------------------------------------------------
'Function: Will remap the colors of any given image to use the palette
' of the given 256 colors (8-bit) destination screen, so that it
' can be displayed on that screen using _PUTIMAGE with as less
' than possible quality loss. For best results this function does
' also apply a Floyd-Steinberg error diffusion matrix on the
' given image to further improve its display quality.
' The original image is not changed, instead the new remapped
' image is returned in a new handle, also _SOURCE & _DEST handles
' are not changed by this function.
' The algorithm is optimized for speed to the fullest extent
' possible on the QB64 language level.
'
'Synopsis: rhan& = RemapImageFS& (ohan&, dhan&)
'
'Result: rhan& --> the handle of the new remapped image, check it for
' validity before use (rhan& < -1, as for any other
' image handles in QB64)
'
'Inputs: ohan& --> the handle of the original source image to remap,
' may be an image of any color depth (1-32 bits)
' dhan& --> the handle of the 8-bit destination image, usually
' the 256 colors screen the image shall be displayed
' on (for the GuiTools Framework this is "appScreen&"),
' note that the destinations color palette must already
' be setup prior calling this function
'
'Notes: For the use of this function a globally shared array must be
' DIMed in an appropriate place: REDIM SHARED fsNearCol%(&HFFFFFF).
' For the GuiTools Framework this is already done within its init
' procedure in file GuiAppFrame.bi.
'---------------------------------------------------------------------
FUNCTION RemapImageFS& (ohan&, dhan&)
RemapImageFS& = -1 'so far return invalid handle
shan& = ohan& 'avoid side effect on given argument
IF shan& < -1 THEN
'--- check/adjust source image & get new 8-bit image ---
swid% = _WIDTH(shan&): shei% = _HEIGHT(shan&)
IF _PIXELSIZE(shan&) <> 4 THEN
than& = _NEWIMAGE(swid%, shei%, 32)
IF than& >= -1 THEN EXIT FUNCTION
_PUTIMAGE , shan&, than&
shan& = than&
ELSE
than& = -1 'avoid freeing below
END IF
nhan& = _NEWIMAGE(swid%, shei%, 256)
'--- Floyd-Steinberg error distribution arrays ---
rhan& = _NEWIMAGE(swid%, 2, 32) 'these are missused as LONG arrays,
ghan& = _NEWIMAGE(swid%, 2, 32) 'with CHECKING:OFF this is much faster
bhan& = _NEWIMAGE(swid%, 2, 32) 'than real QB64 arrays
'--- curr/next row offsets (for distribution array access) ---
cro% = 0: nro% = swid% * 4 'will be swapped after each pixel row
'--- the matrix values are extended by 16384 to avoid slow floating ---
'--- point ops and to allow for integer storage in the above arrays ---
'--- also it's a power of 2, which may be optimized into a bitshift ---
seven% = (7 / 16) * 16384 'X+1,Y+0 error fraction
three% = (3 / 16) * 16384 'X-1,Y+1 error fraction
five% = (5 / 16) * 16384 'X+0,Y+1 error fraction
one% = (1 / 16) * 16384 'X+1,Y+1 error fraction
'--- if all is good, then start remapping ---
$CHECKING:OFF
IF nhan& < -1 AND rhan& < -1 AND ghan& < -1 AND bhan& < -1 THEN
_COPYPALETTE dhan&, nhan& 'dest palette to new image
'--- for speed we do direct memory access ---
DIM sbuf AS _MEM: sbuf = _MEMIMAGE(shan&): soff%& = sbuf.OFFSET
DIM nbuf AS _MEM: nbuf = _MEMIMAGE(nhan&): noff%& = nbuf.OFFSET
DIM rbuf AS _MEM: rbuf = _MEMIMAGE(rhan&): roff%& = rbuf.OFFSET
DIM gbuf AS _MEM: gbuf = _MEMIMAGE(ghan&): goff%& = gbuf.OFFSET
DIM bbuf AS _MEM: bbuf = _MEMIMAGE(bhan&): boff%& = bbuf.OFFSET
'--- iterate through pixels ---
FOR y% = 0 TO shei% - 1
FOR x% = 0 TO swid% - 1
'--- curr/prev/next pixel offsets ---
cpo% = x% * 4: ppo% = cpo% - 4: npo% = cpo% + 4
'--- get pixel ARGB value from source ---
srgb~& = _MEMGET(sbuf, soff%&, _UNSIGNED LONG)
'--- add distributed error, shrink by 16384, clear error ---
'--- current pixel X+0, Y+0 (= cro% (current row offset)) ---
poff% = cro% + cpo% 'pre-calc full pixel offset
sr% = ((srgb~& AND &HFF0000~&) \ 65536) + (_MEMGET(rbuf, roff%& + poff%, LONG) \ 16384) 'red
sg% = ((srgb~& AND &HFF00~&) \ 256) + (_MEMGET(gbuf, goff%& + poff%, LONG) \ 16384) 'green
sb% = (srgb~& AND &HFF~&) + (_MEMGET(bbuf, boff%& + poff%, LONG) \ 16384) 'blue
_MEMPUT rbuf, roff%& + poff%, 0 AS LONG 'clearing each single pixel error using _MEMPUT
_MEMPUT gbuf, goff%& + poff%, 0 AS LONG 'turns out even faster than clearing the entire
_MEMPUT bbuf, boff%& + poff%, 0 AS LONG 'pixel row using _MEMFILL at the end of the loop
'--- find nearest color ---
crgb~& = _RGBA32(sr%, sg%, sb%, 0) 'used for fast value clipping + channel merge
IF fsNearCol%(crgb~&) > 0 THEN
npen% = fsNearCol%(crgb~&) - 1 'already known
ELSE
npen% = _RGB(sr%, sg%, sb%, nhan&) 'not known, find one
fsNearCol%(crgb~&) = npen% + 1 'save for later use
END IF
'--- put colormapped pixel to dest ---
_MEMPUT nbuf, noff%&, npen% AS _UNSIGNED _BYTE
'------------------------------------------
'--- Floyd-Steinberg error distribution ---
'------------------------------------------
'--- You may comment this block out, to see the
'--- result without applied FS matrix.
'-----
'--- get dest palette RGB value, calc error to clipped source ---
nrgb~& = _PALETTECOLOR(npen%, nhan&)
er% = ((crgb~& AND &HFF0000~&) - (nrgb~& AND &HFF0000~&)) \ 65536
eg% = ((crgb~& AND &HFF00~&) - (nrgb~& AND &HFF00~&)) \ 256
eb% = (crgb~& AND &HFF~&) - (nrgb~& AND &HFF~&)
'--- distribute error according to FS matrix ---
IF x% > 0 THEN
'--- X-1, Y+1 (= nro% (next row offset)) ---
poff% = nro% + ppo% 'pre-calc full pixel offset
_MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * three%) AS LONG 'red
_MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * three%) AS LONG 'green
_MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * three%) AS LONG 'blue
END IF
'--- X+0, Y+1 (= nro% (next row offset)) ---
poff% = nro% + cpo% 'pre-calc full pixel offset
_MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * five%) AS LONG 'red
_MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * five%) AS LONG 'green
_MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * five%) AS LONG 'blue
IF x% < (swid% - 1) THEN
'--- X+1, Y+0 (= cro% (current row offset)) ---
poff% = cro% + npo% 'pre-calc full pixel offset
_MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * seven%) AS LONG 'red
_MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * seven%) AS LONG 'green
_MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * seven%) AS LONG 'blue
'--- X+1, Y+1 (= nro% (next row offset)) ---
poff% = nro% + npo% 'pre-calc full pixel offset
_MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * one%) AS LONG 'red
_MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * one%) AS LONG 'green
_MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * one%) AS LONG 'blue
END IF
'------------------------------------------
'--- End of FS ----------------------------
'------------------------------------------
noff%& = noff%& + 1 'next dest pixel
soff%& = soff%& + 4 'next source pixel
NEXT x%
tmp% = cro%: cro% = nro%: nro% = tmp% 'exchange distribution array row offsets
NEXT y%
'--- memory cleanup ---
_MEMFREE bbuf
_MEMFREE gbuf
_MEMFREE rbuf
_MEMFREE nbuf
_MEMFREE sbuf
'--- set result ---
RemapImageFS& = nhan&
nhan& = -1 'avoid freeing below
END IF
$CHECKING:ON
'--- remapping done or error, cleanup remains ---
IF bhan& < -1 THEN _FREEIMAGE bhan&
IF ghan& < -1 THEN _FREEIMAGE ghan&
IF rhan& < -1 THEN _FREEIMAGE rhan&
IF nhan& < -1 THEN _FREEIMAGE nhan&
IF than& < -1 THEN _FREEIMAGE than&
END IF
END FUNCTION
For speed tests you may download The GuiTools Framework and look into the dev_storage folder. There's a program called FSRemapTests.bas which does various benchmarkes for the routine.
Yes, the commenting method I used separates the code nicely for myself and everyone else. I wanted to make sure that whenever I revisit this code the comments will give me a complete understanding of what is going on. Nothing worse than going back to old code and wondering, "What the heck was I doing here?".