Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Image Manipulation Routines
#5
(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.

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.
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!

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?".
New to QB64pe? Visit the QB64 tutorial to get started.
QB64 Tutorial
Reply


Messages In This Thread
Image Manipulation Routines - by TerryRitchie - 02-20-2024, 06:25 PM
RE: Image Manipulation Routines - by RhoSigma - 02-20-2024, 07:45 PM
RE: Image Manipulation Routines - by TerryRitchie - 02-21-2024, 03:34 PM
RE: Image Manipulation Routines - by NakedApe - 02-21-2024, 01:01 AM
RE: Image Manipulation Routines - by Pete - 02-21-2024, 01:07 AM
RE: Image Manipulation Routines - by TerryRitchie - 02-21-2024, 03:41 PM
RE: Image Manipulation Routines - by Pete - 02-22-2024, 08:49 AM
RE: Image Manipulation Routines - by OldMoses - 02-23-2024, 12:33 PM
RE: Image Manipulation Routines - by TerryRitchie - 02-23-2024, 03:59 PM



Users browsing this thread: 2 Guest(s)