Author Topic: Question for RhoSigma  (Read 3378 times)

0 Members and 1 Guest are viewing this topic.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Question for RhoSigma
« on: August 12, 2019, 07:21:18 am »
Code: QB64: [Select]
  1. DECLARE LIBRARY "QB64GuiTools\dev_framework\GuiAppFrame" 'Do not add .h here !!
  2.     FUNCTION FindColor& (BYVAL r&, BYVAL g&, BYVAL b&, BYVAL i&, BYVAL mi&, BYVAL ma&)
  3.     'This is a replacement for the _RGB function. It works for upto 8-bit
  4.     '(256 colors) images only and needs a valid image. It can limit the
  5.     'number of pens to search and uses a better color matching algorithm.
  6.  
  7.  
  8. REDIM SHARED fsNearCol%(&HFFFFFF)
  9.  
  10. ws = _NEWIMAGE(640, 480, 32) 'A 32 bit screen
  11. ts = _NEWIMAGE(640, 480, 256) 'A 256 bit screen to provide the default QB64 palette
  12. FOR i = 1 TO 100 '100 random colors
  13.     LINE (RND * 640, RND * 480)-(RND * 640, RND * 480), &HFF000000 + RND * &HFFFFFF, BF
  14.  
  15. nh = RemapImageFS&(ws, ts) 'Remap from the 32-bit to the 256 color screen
  16. SLEEP 'pause to view original
  17. SCREEN nh 'show the remapped 256 color screen
  18.  
  19. '---------------------------------------------------------------------
  20. 'Function: Will remap the colors of any given image to use the palette
  21. '          of the given 256 colors (8-bit) destination screen, so that it
  22. '          can be displayed on that screen using _PUTIMAGE with as less
  23. '          than possible quality loss. For best results this function does
  24. '          also apply a Floyd-Steinberg error diffusion matrix on the
  25. '          given image to further improve its display quality.
  26. '           The original image is not changed, instead the new remapped
  27. '          image is returned in a new handle, also _SOURCE & _DEST handles
  28. '          are not changed by this function.
  29. '           The algorithm is optimized for speed to the fullest extent
  30. '          possible on the QB64 language level.
  31. '
  32. 'Synopsis: rhan& = RemapImageFS& (ohan&, dhan&)
  33. '
  34. 'Result:   rhan& --> the handle of the new remapped image, check it for
  35. '                    validity before use (rhan& < -1, as for any other
  36. '                    image handles in QB64)
  37. '
  38. 'Inputs:   ohan& --> the handle of the original source image to remap,
  39. '                    may be an image of any color depth (1-32 bits)
  40. '          dhan& --> the handle of the 8-bit destination image, usually
  41. '                    the 256 colors screen the image shall be displayed
  42. '                    on (for the GuiTools Framework this is "appScreen&"),
  43. '                    note that the destinations color palette must already
  44. '                    be setup prior calling this function
  45. '
  46. 'Notes:    For the use of this function a globally shared array must be
  47. '          DIMed in an appropriate place: REDIM SHARED fsNearCol%(&HFFFFFF).
  48. '          For the GuiTools Framework this is already done within its init
  49. '          procedure in file GuiAppFrame.bi.
  50. '---------------------------------------------------------------------
  51. FUNCTION RemapImageFS& (ohan&, dhan&)
  52.     RemapImageFS& = -1 'so far return invalid handle
  53.     shan& = ohan& 'avoid side effect on given argument
  54.     IF shan& < -1 THEN
  55.         '--- check/adjust source image & get new 8-bit image ---
  56.         swid% = _WIDTH(shan&): shei% = _HEIGHT(shan&)
  57.         IF _PIXELSIZE(shan&) <> 4 THEN
  58.             than& = _NEWIMAGE(swid%, shei%, 32)
  59.             IF than& >= -1 THEN EXIT FUNCTION
  60.             _PUTIMAGE , shan&, than&
  61.             shan& = than&
  62.         ELSE
  63.             than& = -1 'avoid freeing below
  64.         END IF
  65.         nhan& = _NEWIMAGE(swid%, shei%, 256)
  66.         '--- Floyd-Steinberg error distribution arrays ---
  67.         rhan& = _NEWIMAGE(swid%, 2, 32) 'these are missused as LONG arrays,
  68.         ghan& = _NEWIMAGE(swid%, 2, 32) 'with CHECKING:OFF this is much faster
  69.         bhan& = _NEWIMAGE(swid%, 2, 32) 'than real QB64 arrays
  70.         '--- curr/next row offsets (for distribution array access) ---
  71.         cro% = 0: nro% = swid% * 4 'will be swapped after each pixel row
  72.         '--- the matrix values are extended by 16384 to avoid slow floating ---
  73.         '--- point ops and to allow for integer storage in the above arrays ---
  74.         '--- also it's a power of 2, which may be optimized into a bitshift ---
  75.         seven% = (7 / 16) * 16384 'X+1,Y+0 error fraction
  76.         three% = (3 / 16) * 16384 'X-1,Y+1 error fraction
  77.         five% = (5 / 16) * 16384 'X+0,Y+1 error fraction
  78.         one% = (1 / 16) * 16384 'X+1,Y+1 error fraction
  79.         '--- if all is good, then start remapping ---
  80.         '$CHECKING:OFF
  81.         IF nhan& < -1 AND rhan& < -1 AND ghan& < -1 AND bhan& < -1 THEN
  82.             _COPYPALETTE dhan&, nhan& 'dest palette to new image
  83.             '--- for speed we do direct memory access ---
  84.             DIM sbuf AS _MEM: sbuf = _MEMIMAGE(shan&): soff%& = sbuf.OFFSET
  85.             DIM nbuf AS _MEM: nbuf = _MEMIMAGE(nhan&): noff%& = nbuf.OFFSET
  86.             DIM rbuf AS _MEM: rbuf = _MEMIMAGE(rhan&): roff%& = rbuf.OFFSET
  87.             DIM gbuf AS _MEM: gbuf = _MEMIMAGE(ghan&): goff%& = gbuf.OFFSET
  88.             DIM bbuf AS _MEM: bbuf = _MEMIMAGE(bhan&): boff%& = bbuf.OFFSET
  89.             '--- iterate through pixels ---
  90.             FOR y% = 0 TO shei% - 1
  91.                 FOR x% = 0 TO swid% - 1
  92.                     '--- curr/prev/next pixel offsets ---
  93.                     cpo% = x% * 4: ppo% = cpo% - 4: npo% = cpo% + 4
  94.                     '--- get pixel ARGB value from source ---
  95.                     srgb~& = _MEMGET(sbuf, soff%&, _UNSIGNED LONG)
  96.                     '--- add distributed error, shrink by 16384, clear error ---
  97.                     '--- current pixel X+0, Y+0 (= cro% (current row offset)) ---
  98.                     poff% = cro% + cpo% 'pre-calc full pixel offset
  99.                     sr% = ((srgb~& AND &HFF0000~&) \ 65536) + (_MEMGET(rbuf, roff%& + poff%, LONG) \ 16384) 'red
  100.                     sg% = ((srgb~& AND &HFF00~&) \ 256) + (_MEMGET(gbuf, goff%& + poff%, LONG) \ 16384) 'green
  101.                     sb% = (srgb~& AND &HFF~&) + (_MEMGET(bbuf, boff%& + poff%, LONG) \ 16384) 'blue
  102.                     _MEMPUT rbuf, roff%& + poff%, 0 AS LONG 'clearing each single pixel error using _MEMPUT
  103.                     _MEMPUT gbuf, goff%& + poff%, 0 AS LONG 'turns out even faster than clearing the entire
  104.                     _MEMPUT bbuf, boff%& + poff%, 0 AS LONG 'pixel row using _MEMFILL at the end of the loop
  105.                     '--- find nearest color ---
  106.                     crgb~& = _RGBA32(sr%, sg%, sb%, 0) 'used for fast value clipping + channel merge
  107.                     IF fsNearCol%(crgb~&) > 0 THEN
  108.                         npen% = fsNearCol%(crgb~&) - 1 'already known
  109.                     ELSE
  110.                         npen% = FindColor&(sr%, sg%, sb%, nhan&, 24, 255 - guiReservedPens%) 'not known, find one
  111.                         fsNearCol%(crgb~&) = npen% + 1 'save for later use
  112.                     END IF
  113.                     '--- put colormapped pixel to dest ---
  114.                     _MEMPUT nbuf, noff%&, npen% AS _UNSIGNED _BYTE
  115.                     '------------------------------------------
  116.                     '--- Floyd-Steinberg error distribution ---
  117.                     '------------------------------------------
  118.                     '--- You may comment this block out, to see the
  119.                     '--- result without applied FS matrix.
  120.                     '-----
  121.                     '--- get dest palette RGB value, calc error to clipped source ---
  122.                     nrgb~& = _PALETTECOLOR(npen%, nhan&)
  123.                     er% = ((crgb~& AND &HFF0000~&) - (nrgb~& AND &HFF0000~&)) \ 65536
  124.                     eg% = ((crgb~& AND &HFF00~&) - (nrgb~& AND &HFF00~&)) \ 256
  125.                     eb% = (crgb~& AND &HFF~&) - (nrgb~& AND &HFF~&)
  126.                     '--- distribute error according to FS matrix ---
  127.                     IF x% > 0 THEN
  128.                         '--- X-1, Y+1 (= nro% (next row offset)) ---
  129.                         poff% = nro% + ppo% 'pre-calc full pixel offset
  130.                         _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * three%) AS LONG 'red
  131.                         _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * three%) AS LONG 'green
  132.                         _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * three%) AS LONG 'blue
  133.                     END IF
  134.                     '--- X+0, Y+1 (= nro% (next row offset)) ---
  135.                     poff% = nro% + cpo% 'pre-calc full pixel offset
  136.                     _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * five%) AS LONG 'red
  137.                     _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * five%) AS LONG 'green
  138.                     _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * five%) AS LONG 'blue
  139.                     IF x% < (swid% - 1) THEN
  140.                         '--- X+1, Y+0 (= cro% (current row offset)) ---
  141.                         poff% = cro% + npo% 'pre-calc full pixel offset
  142.                         _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * seven%) AS LONG 'red
  143.                         _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * seven%) AS LONG 'green
  144.                         _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * seven%) AS LONG 'blue
  145.                         '--- X+1, Y+1 (= nro% (next row offset)) ---
  146.                         poff% = nro% + npo% 'pre-calc full pixel offset
  147.                         _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * one%) AS LONG 'red
  148.                         _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * one%) AS LONG 'green
  149.                         _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * one%) AS LONG 'blue
  150.                     END IF
  151.                     '------------------------------------------
  152.                     '--- End of FS ----------------------------
  153.                     '------------------------------------------
  154.                     noff%& = noff%& + 1 'next dest pixel
  155.                     soff%& = soff%& + 4 'next source pixel
  156.                 NEXT x%
  157.                 tmp% = cro%: cro% = nro%: nro% = tmp% 'exchange distribution array row offsets
  158.             NEXT y%
  159.             '--- memory cleanup ---
  160.             _MEMFREE bbuf
  161.             _MEMFREE gbuf
  162.             _MEMFREE rbuf
  163.             _MEMFREE nbuf
  164.             _MEMFREE sbuf
  165.             '--- set result ---
  166.             RemapImageFS& = nhan&
  167.             nhan& = -1 'avoid freeing below
  168.         END IF
  169.         $CHECKING:ON
  170.         '--- remapping done or error, cleanup remains ---
  171.         IF bhan& < -1 THEN _FREEIMAGE bhan&
  172.         IF ghan& < -1 THEN _FREEIMAGE ghan&
  173.         IF rhan& < -1 THEN _FREEIMAGE rhan&
  174.         IF nhan& < -1 THEN _FREEIMAGE nhan&
  175.         IF than& < -1 THEN _FREEIMAGE than&
  176.     END IF
  177.  

Playing around a bit with the Floyd-Steinberg color reduction routine from your GUI Framework library, I came up with this little set of code.  (You might want to add a  note that DECLARE LIBRARY is necessary for the FindColor& Function to be found and work properly, since it's called in the subroutine.)

Question: Is there some way to remove those dots that the converted image creates? We're only using 100 colors for the screen palette (101 if we consider 0 red, 0 green, 0 blue, 0 alpha (transparent) as a color).  Shouldn't this convert to the closest palette match without those artifacts?  What's the dots in there for?  And is there a way to remove them?

All I was really looking for, was a simple means to convert 32-bit images down to 256 color images, so that I could then save them in a suitable GIF format.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Question for RhoSigma
« Reply #1 on: August 12, 2019, 09:48:55 am »
As long as the total color palette is less than 256 colors, I can use your routine to gather the palette, and then assign the original values to a 256-color image without any stray artifacts being required at all, like so:

Code: QB64: [Select]
  1. DECLARE LIBRARY "QB64GuiTools\dev_framework\GuiAppFrame" 'Do not add .h here !!
  2.     FUNCTION FindColor& (BYVAL r&, BYVAL g&, BYVAL b&, BYVAL i&, BYVAL mi&, BYVAL ma&)
  3.     'This is a replacement for the _RGB function. It works for upto 8-bit
  4.     '(256 colors) images only and needs a valid image. It can limit the
  5.     'number of pens to search and uses a better color matching algorithm.
  6.  
  7.  
  8. REDIM SHARED fsNearCol%(&HFFFFFF)
  9.  
  10. ws = _NEWIMAGE(640, 480, 32) 'A 32 bit screen
  11. ts = _NEWIMAGE(640, 480, 256) 'A 256 bit screen to provide the default QB64 palette
  12. FOR x = 0 TO 639 STEP 64 '10 steps
  13.     FOR y = 0 TO 479 STEP 48 '10 steps = 100 total boxes
  14.         LINE (x, y)-STEP(64, 48), &HFF000000 + RND * &HFFFFFF, BF
  15.     NEXT
  16. SLEEP 'pause to view original
  17.  
  18.  
  19. nh = RemapImageFS&(ws, ts) 'Remap from the 32-bit to the 256 color screen
  20. SCREEN nh 'show the remapped 256 color screen
  21. SLEEP 'pause so we can see it
  22.  
  23. nh2 = Image32To256(ws, nh) 'Remap from the 32-bit to the 256 color screen, using the palette chosen in RemapImageFS
  24. SCREEN nh2 'show the remapped 256 color screen
  25. SLEEP 'pause so we can see it
  26.  
  27.  
  28. '---------------------------------------------------------------------
  29. 'Function: Will remap the colors of any given image to use the palette
  30. '          of the given 256 colors (8-bit) destination screen, so that it
  31. '          can be displayed on that screen using _PUTIMAGE with as less
  32. '          than possible quality loss. For best results this function does
  33. '          also apply a Floyd-Steinberg error diffusion matrix on the
  34. '          given image to further improve its display quality.
  35. '           The original image is not changed, instead the new remapped
  36. '          image is returned in a new handle, also _SOURCE & _DEST handles
  37. '          are not changed by this function.
  38. '           The algorithm is optimized for speed to the fullest extent
  39. '          possible on the QB64 language level.
  40. '
  41. 'Synopsis: rhan& = RemapImageFS& (ohan&, dhan&)
  42. '
  43. 'Result:   rhan& --> the handle of the new remapped image, check it for
  44. '                    validity before use (rhan& < -1, as for any other
  45. '                    image handles in QB64)
  46. '
  47. 'Inputs:   ohan& --> the handle of the original source image to remap,
  48. '                    may be an image of any color depth (1-32 bits)
  49. '          dhan& --> the handle of the 8-bit destination image, usually
  50. '                    the 256 colors screen the image shall be displayed
  51. '                    on (for the GuiTools Framework this is "appScreen&"),
  52. '                    note that the destinations color palette must already
  53. '                    be setup prior calling this function
  54. '
  55. 'Notes:    For the use of this function a globally shared array must be
  56. '          DIMed in an appropriate place: REDIM SHARED fsNearCol%(&HFFFFFF).
  57. '          For the GuiTools Framework this is already done within its init
  58. '          procedure in file GuiAppFrame.bi.
  59. '---------------------------------------------------------------------
  60. FUNCTION RemapImageFS& (ohan&, dhan&)
  61.     RemapImageFS& = -1 'so far return invalid handle
  62.     shan& = ohan& 'avoid side effect on given argument
  63.     IF shan& < -1 THEN
  64.         '--- check/adjust source image & get new 8-bit image ---
  65.         swid% = _WIDTH(shan&): shei% = _HEIGHT(shan&)
  66.         IF _PIXELSIZE(shan&) <> 4 THEN
  67.             than& = _NEWIMAGE(swid%, shei%, 32)
  68.             IF than& >= -1 THEN EXIT FUNCTION
  69.             _PUTIMAGE , shan&, than&
  70.             shan& = than&
  71.         ELSE
  72.             than& = -1 'avoid freeing below
  73.         END IF
  74.         nhan& = _NEWIMAGE(swid%, shei%, 256)
  75.         '--- Floyd-Steinberg error distribution arrays ---
  76.         rhan& = _NEWIMAGE(swid%, 2, 32) 'these are missused as LONG arrays,
  77.         ghan& = _NEWIMAGE(swid%, 2, 32) 'with CHECKING:OFF this is much faster
  78.         bhan& = _NEWIMAGE(swid%, 2, 32) 'than real QB64 arrays
  79.         '--- curr/next row offsets (for distribution array access) ---
  80.         cro% = 0: nro% = swid% * 4 'will be swapped after each pixel row
  81.         '--- the matrix values are extended by 16384 to avoid slow floating ---
  82.         '--- point ops and to allow for integer storage in the above arrays ---
  83.         '--- also it's a power of 2, which may be optimized into a bitshift ---
  84.         seven% = (7 / 16) * 16384 'X+1,Y+0 error fraction
  85.         three% = (3 / 16) * 16384 'X-1,Y+1 error fraction
  86.         five% = (5 / 16) * 16384 'X+0,Y+1 error fraction
  87.         one% = (1 / 16) * 16384 'X+1,Y+1 error fraction
  88.         '--- if all is good, then start remapping ---
  89.         $CHECKING:OFF
  90.         IF nhan& < -1 AND rhan& < -1 AND ghan& < -1 AND bhan& < -1 THEN
  91.             _COPYPALETTE dhan&, nhan& 'dest palette to new image
  92.             '--- for speed we do direct memory access ---
  93.             DIM sbuf AS _MEM: sbuf = _MEMIMAGE(shan&): soff%& = sbuf.OFFSET
  94.             DIM nbuf AS _MEM: nbuf = _MEMIMAGE(nhan&): noff%& = nbuf.OFFSET
  95.             DIM rbuf AS _MEM: rbuf = _MEMIMAGE(rhan&): roff%& = rbuf.OFFSET
  96.             DIM gbuf AS _MEM: gbuf = _MEMIMAGE(ghan&): goff%& = gbuf.OFFSET
  97.             DIM bbuf AS _MEM: bbuf = _MEMIMAGE(bhan&): boff%& = bbuf.OFFSET
  98.             '--- iterate through pixels ---
  99.             FOR y% = 0 TO shei% - 1
  100.                 FOR x% = 0 TO swid% - 1
  101.                     '--- curr/prev/next pixel offsets ---
  102.                     cpo% = x% * 4: ppo% = cpo% - 4: npo% = cpo% + 4
  103.                     '--- get pixel ARGB value from source ---
  104.                     srgb~& = _MEMGET(sbuf, soff%&, _UNSIGNED LONG)
  105.                     '--- add distributed error, shrink by 16384, clear error ---
  106.                     '--- current pixel X+0, Y+0 (= cro% (current row offset)) ---
  107.                     poff% = cro% + cpo% 'pre-calc full pixel offset
  108.                     sr% = ((srgb~& AND &HFF0000~&) \ 65536) + (_MEMGET(rbuf, roff%& + poff%, LONG) \ 16384) 'red
  109.                     sg% = ((srgb~& AND &HFF00~&) \ 256) + (_MEMGET(gbuf, goff%& + poff%, LONG) \ 16384) 'green
  110.                     sb% = (srgb~& AND &HFF~&) + (_MEMGET(bbuf, boff%& + poff%, LONG) \ 16384) 'blue
  111.                     _MEMPUT rbuf, roff%& + poff%, 0 AS LONG 'clearing each single pixel error using _MEMPUT
  112.                     _MEMPUT gbuf, goff%& + poff%, 0 AS LONG 'turns out even faster than clearing the entire
  113.                     _MEMPUT bbuf, boff%& + poff%, 0 AS LONG 'pixel row using _MEMFILL at the end of the loop
  114.                     '--- find nearest color ---
  115.                     crgb~& = _RGBA32(sr%, sg%, sb%, 0) 'used for fast value clipping + channel merge
  116.                     IF fsNearCol%(crgb~&) > 0 THEN
  117.                         npen% = fsNearCol%(crgb~&) - 1 'already known
  118.                     ELSE
  119.                         npen% = FindColor&(sr%, sg%, sb%, nhan&, 24, 255 - guiReservedPens%) 'not known, find one
  120.                         fsNearCol%(crgb~&) = npen% + 1 'save for later use
  121.                     END IF
  122.                     '--- put colormapped pixel to dest ---
  123.                     _MEMPUT nbuf, noff%&, npen% AS _UNSIGNED _BYTE
  124.                     '------------------------------------------
  125.                     '--- Floyd-Steinberg error distribution ---
  126.                     '------------------------------------------
  127.                     '--- You may comment this block out, to see the
  128.                     '--- result without applied FS matrix.
  129.                     '-----
  130.                     '--- get dest palette RGB value, calc error to clipped source ---
  131.                     nrgb~& = _PALETTECOLOR(npen%, nhan&)
  132.                     er% = ((crgb~& AND &HFF0000~&) - (nrgb~& AND &HFF0000~&)) \ 65536
  133.                     eg% = ((crgb~& AND &HFF00~&) - (nrgb~& AND &HFF00~&)) \ 256
  134.                     eb% = (crgb~& AND &HFF~&) - (nrgb~& AND &HFF~&)
  135.                     '--- distribute error according to FS matrix ---
  136.                     IF x% > 0 THEN
  137.                         '--- X-1, Y+1 (= nro% (next row offset)) ---
  138.                         poff% = nro% + ppo% 'pre-calc full pixel offset
  139.                         _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * three%) AS LONG 'red
  140.                         _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * three%) AS LONG 'green
  141.                         _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * three%) AS LONG 'blue
  142.                     END IF
  143.                     '--- X+0, Y+1 (= nro% (next row offset)) ---
  144.                     poff% = nro% + cpo% 'pre-calc full pixel offset
  145.                     _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * five%) AS LONG 'red
  146.                     _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * five%) AS LONG 'green
  147.                     _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * five%) AS LONG 'blue
  148.                     IF x% < (swid% - 1) THEN
  149.                         '--- X+1, Y+0 (= cro% (current row offset)) ---
  150.                         poff% = cro% + npo% 'pre-calc full pixel offset
  151.                         _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * seven%) AS LONG 'red
  152.                         _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * seven%) AS LONG 'green
  153.                         _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * seven%) AS LONG 'blue
  154.                         '--- X+1, Y+1 (= nro% (next row offset)) ---
  155.                         poff% = nro% + npo% 'pre-calc full pixel offset
  156.                         _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * one%) AS LONG 'red
  157.                         _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * one%) AS LONG 'green
  158.                         _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * one%) AS LONG 'blue
  159.                     END IF
  160.                     '------------------------------------------
  161.                     '--- End of FS ----------------------------
  162.                     '------------------------------------------
  163.                     noff%& = noff%& + 1 'next dest pixel
  164.                     soff%& = soff%& + 4 'next source pixel
  165.                 NEXT x%
  166.                 tmp% = cro%: cro% = nro%: nro% = tmp% 'exchange distribution array row offsets
  167.             NEXT y%
  168.             '--- memory cleanup ---
  169.             _MEMFREE bbuf
  170.             _MEMFREE gbuf
  171.             _MEMFREE rbuf
  172.             _MEMFREE nbuf
  173.             _MEMFREE sbuf
  174.             '--- set result ---
  175.             RemapImageFS& = nhan&
  176.             nhan& = -1 'avoid freeing below
  177.         END IF
  178.         $CHECKING:ON
  179.         '--- remapping done or error, cleanup remains ---
  180.         IF bhan& < -1 THEN _FREEIMAGE bhan&
  181.         IF ghan& < -1 THEN _FREEIMAGE ghan&
  182.         IF rhan& < -1 THEN _FREEIMAGE rhan&
  183.         IF nhan& < -1 THEN _FREEIMAGE nhan&
  184.         IF than& < -1 THEN _FREEIMAGE than&
  185.     END IF
  186.  
  187. FUNCTION Image32To256 (image&, image2&)
  188.     DIM o AS _OFFSET
  189.  
  190.     Image32To256 = _NEWIMAGE(_WIDTH(image&), _HEIGHT(image&), 256)
  191.     DIM m(1) AS _MEM: m(0) = _MEMIMAGE(image&): m(1) = _MEMIMAGE(Image32To256)
  192.     DO
  193.         _MEMGET m(0), m(0).OFFSET + o + 3, a
  194.         _MEMGET m(0), m(0).OFFSET + o + 2, r
  195.         _MEMGET m(0), m(0).OFFSET + o + 1, g
  196.         _MEMGET m(0), m(0).OFFSET + o + 0, b
  197.         _MEMPUT m(1), m(1).OFFSET + o \ 4, _RGBA(r, g, b, a, image2&) AS _UNSIGNED _BYTE
  198.         o = o + 4
  199.     LOOP UNTIL o >= m(0).SIZE

A hybrid of this method for <= 256 colors, and your method if > 256 colors, might end up being the way I go when having to convert a 32-bit screen down to a 256 color image.  ;)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
    • View Profile
Re: Question for RhoSigma
« Reply #2 on: August 12, 2019, 10:09:45 am »
Quote
Question: Is there some way to remove those dots that the converted image creates? We're only using 100 colors for the screen palette (101 if we consider 0 red, 0 green, 0 blue, 0 alpha (transparent) as a color).  Shouldn't this convert to the closest palette match without those artifacts?  What's the dots in there for?  And is there a way to remove them?

Those dots are comming from the actual Floyd-Steinberg error distribution. The algorithm uses all allowed colors (see note below) in the target palette and tries to keep the original 32-bit color as close as possible using this dithering effect. This effect mostly comes up for those clipart-like images where large parts have the same color, it's usually not that obvious in real color (digi cam) pictures.

However, if you just want to generate a closest color image, then you may waive to the Floyd-Steinberg dithering simply by commenting (or deleting) lines 118-156.

Note to allowed colors:
here is a better prototype of the FindColor&() function,
  FUNCTION FindColor& (BYVAL red&, BYVAL green&, BYVAL blue&, BYVAL image&, BYVAL minColor&, BYVAL maxColor&)
as you see you've a min/max color parameter to limit the palette entries to search for a match. You can give -1 for unused limits. Set both min & max to -1 will search the entire palette 0-255, hence same as _RGB does.

That's what FindColor&() basically is, it's a copy of the matchcol() function (from libqb), which I've tweaked a bit to allow for the pen limits and using a (IMHO) better color matching method.
So in general, if you don't need the limit parameters, then you may exchange the FindColor&() call by a call to _RGB, then you don't need the DECLARE LIBRARY thing either anymore.

The fsNearCol%() array is mainly for speedup purposes in GuiTools, it saves once found color matches, to save some time when processing additional images, note that this array must be cleared (REDIMed) when the target palette changes. For your purpose you can probably wave to it too, by commenting (deleting) the lines 110-115 except 113 (the FindColor&()/_RGB call).

EDIT: line numbers according to your first post :)
« Last Edit: August 12, 2019, 10:14:00 am by RhoSigma »
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Question for RhoSigma
« Reply #3 on: August 13, 2019, 01:27:40 pm »
One thing I did notice, which you might consider altering in the routines:

    IF shan& < -1 THEN

I’ve changed those to the following, for my personal needs (and library usage):

    IF shan& < -1 OR shan& = 0 THEN



Reason?

0 is often how we refer to the current view screen.

SCREEN _NEWIMAGE(640,480,32)
...do stuff

NewScreen = RemapImageFS(0, 256ColorIndexScreen)

^ That 0 there will currently skip the routine and return us an invalid handle.  With the change, it’ll make you a 256 color screen of the current display, and then return that new handle back to you.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
    • View Profile
Re: Question for RhoSigma
« Reply #4 on: August 13, 2019, 01:35:06 pm »
Yes, that makes sense, will adjust this in GuiTools for the next release...
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack