Author Topic: Convert 32-bit images to 256 color screens  (Read 3660 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
Convert 32-bit images to 256 color screens
« on: August 12, 2019, 06:52:15 pm »
And, after much brain melting, I think I've finally pieced together a suitable method to convert 32-bit images down to 256 colors for use with QB64.  Needless to say, you'll need the attached files to run this demo:

Code: QB64: [Select]
  1. DECLARE LIBRARY "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. REDIM SHARED fsNearCol%(&HFFFFFF)
  8.  
  9. ws = _NEWIMAGE(640, 480, 32) 'A 32 bit screen
  10. ts = _NEWIMAGE(640, 480, 256) 'A 256 color screen, which is only used so I can get the standard 256 color paletter from it.
  11. DIM color256 AS _UNSIGNED LONG
  12.  
  13. CLS , _RGB32(0, 0, 0)
  14.  
  15. FOR j = 1 TO 2
  16.     IF j = 1 THEN
  17.         FOR i = 1 TO 100 '100 random colors
  18.             'if we want to use the standard 256 color screen palette, we can do so as below
  19.             color256 = _RGB32(_RED(i, ts), _GREEN(i, ts), _BLUE(i, ts))
  20.             LINE (RND * 640, RND * 480)-(RND * 640, RND * 480), color256, BF
  21.         NEXT
  22.     ELSE 'we can go with completely random colors with the following instead:
  23.         FOR i = 1 TO 100 '100 random colors
  24.             LINE (RND * 640, RND * 480)-(RND * 640, RND * 480), &HF0000000 + RND * &HFFFFFFF, BF
  25.         NEXT
  26.     END IF
  27.     PRINT "This is the original screen, pass"; j
  28.     SLEEP 'show the original screen
  29.  
  30.     t = Image32To256(ws)
  31.     SCREEN t 'show the standard 256 image screen with the image converted over
  32.     '         this keeps us from having to learn or use any new/unique palettes the image may have
  33.     '         but, it does cause us to lose details and hues.
  34.     PRINT "This is the 256-color screen, pass"; j
  35.     SLEEP
  36.     SCREEN ws
  37.     _FREEIMAGE t
  38.     CLS
  39.  
  40.  
  41. l = _LOADIMAGE("C:\QB64 Repo\Steve64\Beautiful_colorful_bird_wallpaper01.jpg", 32)
  42. _SCREENMOVE 0, 0 'move the screen to use as much of the screen as possible, since it's so damn huge!
  43. PRINT "This is the original 32-bit screen."
  44. SLEEP 'to show the 32-bit image of the colorful bird I found
  45.  
  46. t = Image32To256(l)
  47. SCREEN t 'show the 256 image screen with the image converted over
  48. _SCREENMOVE 0, 0 'move this one too!
  49. PRINT "This is the converted 256 color screen."
  50. 'And we're done.  You should now be seeing a pretty little 256 color version of the bird
  51.  
  52.  
  53. FUNCTION Image32To256 (image&)
  54.     DIM o AS _OFFSET
  55.     DIM t AS _UNSIGNED LONG, color256 AS _UNSIGNED LONG
  56.     DIM index256 AS _UNSIGNED LONG
  57.     TYPE Pal_type
  58.         c AS _UNSIGNED LONG 'color index
  59.         n AS LONG 'number of times it appears
  60.     END TYPE
  61.     DIM Pal(255) AS _UNSIGNED LONG
  62.     I256 = _NEWIMAGE(_WIDTH(image&), _HEIGHT(image&), 256)
  63.     DIM m(1) AS _MEM: m(0) = _MEMIMAGE(image&): m(1) = _MEMIMAGE(I256)
  64.     DO 'get the palette and number of colors used
  65.         _MEMGET m(0), m(0).OFFSET + o, t 'Get the colors from the original screen
  66.         FOR i = 0 TO colors 'check to see if they're in the existing palette we're making
  67.             IF Pal(i) = t THEN EXIT FOR
  68.         NEXT
  69.         IF i > colors THEN
  70.             Pal(colors) = t
  71.             colors = colors + 1 'increment the index for the new color found
  72.             IF colors > 255 THEN 'no need to check any further; it's not a normal QB64 256 color image
  73.                 Image32To256 = RemapImageFS(image&, I256)
  74.                 _FREEIMAGE I256
  75.                 _MEMFREE m()
  76.                 EXIT FUNCTION 'and we're done, with 100% image compatability saved
  77.             END IF
  78.         END IF
  79.         o = o + 4
  80.     LOOP UNTIL o >= m(0).SIZE
  81.  
  82.     '  we might be working with a standard qb64 256 color screen
  83.     '  check for that first
  84.     colors = colors - 1 'back up one, as we found our limit and aren't needing to set another
  85.     FOR i = 0 TO colors 'comparing palette against QB64 256 color palette
  86.         t = Pal(i)
  87.         index256 = _RGBA(_RED(t), _GREEN(t), _BLUE(t), _ALPHA(t), I256)
  88.         color256 = _RGBA32(_RED(index256, I256), _GREEN(index256, I256), _BLUE(index256, I256), _ALPHA(index256, I256))
  89.         IF t <> color256 THEN NSCU = -1: EXIT FOR
  90.     NEXT
  91.     IF NSCU THEN 'it's not a standard QB64 256 color palette, but it's still less than 256 total colors.
  92.         FOR i = 0 TO colors: _PALETTECOLOR i, Pal(i), I256: NEXT 'set the palette
  93.     END IF
  94.     'If we didn't change the palette above, we should work 100% with qb64's internal 256 color palette
  95.     o = 0
  96.     DO 'Get the colors, put them to a 256 color screen, as is
  97.         _MEMGET m(0), m(0).OFFSET + o + 3, a
  98.         _MEMGET m(0), m(0).OFFSET + o + 2, r
  99.         _MEMGET m(0), m(0).OFFSET + o + 1, g
  100.         _MEMGET m(0), m(0).OFFSET + o + 0, b
  101.         _MEMPUT m(1), m(1).OFFSET + o \ 4, _RGBA(r, g, b, a, I256) AS _UNSIGNED _BYTE
  102.         o = o + 4
  103.     LOOP UNTIL o >= m(0).SIZE
  104.     _MEMFREE m()
  105.     Image32To256 = I256
  106.  
  107. FUNCTION RemapImageFS& (ohan&, dhan&)
  108.     RemapImageFS& = -1 'so far return invalid handle
  109.     shan& = ohan& 'avoid side effect on given argument
  110.     IF shan& < -1 THEN
  111.         '--- check/adjust source image & get new 8-bit image ---
  112.         swid% = _WIDTH(shan&): shei% = _HEIGHT(shan&)
  113.         IF _PIXELSIZE(shan&) <> 4 THEN
  114.             than& = _NEWIMAGE(swid%, shei%, 32)
  115.             IF than& >= -1 THEN EXIT FUNCTION
  116.             _PUTIMAGE , shan&, than&
  117.             shan& = than&
  118.         ELSE
  119.             than& = -1 'avoid freeing below
  120.         END IF
  121.         nhan& = _NEWIMAGE(swid%, shei%, 256)
  122.         '--- Floyd-Steinberg error distribution arrays ---
  123.         rhan& = _NEWIMAGE(swid%, 2, 32) 'these are missused as LONG arrays,
  124.         ghan& = _NEWIMAGE(swid%, 2, 32) 'with CHECKING:OFF this is much faster
  125.         bhan& = _NEWIMAGE(swid%, 2, 32) 'than real QB64 arrays
  126.         '--- curr/next row offsets (for distribution array access) ---
  127.         cro% = 0: nro% = swid% * 4 'will be swapped after each pixel row
  128.         '--- the matrix values are extended by 16384 to avoid slow floating ---
  129.         '--- point ops and to allow for integer storage in the above arrays ---
  130.         '--- also it's a power of 2, which may be optimized into a bitshift ---
  131.         seven% = (7 / 16) * 16384 'X+1,Y+0 error fraction
  132.         three% = (3 / 16) * 16384 'X-1,Y+1 error fraction
  133.         five% = (5 / 16) * 16384 'X+0,Y+1 error fraction
  134.         one% = (1 / 16) * 16384 'X+1,Y+1 error fraction
  135.         '--- if all is good, then start remapping ---
  136.         $CHECKING:OFF
  137.         IF nhan& < -1 AND rhan& < -1 AND ghan& < -1 AND bhan& < -1 THEN
  138.             _COPYPALETTE dhan&, nhan& 'dest palette to new image
  139.             '--- for speed we do direct memory access ---
  140.             DIM sbuf AS _MEM: sbuf = _MEMIMAGE(shan&): soff%& = sbuf.OFFSET
  141.             DIM nbuf AS _MEM: nbuf = _MEMIMAGE(nhan&): noff%& = nbuf.OFFSET
  142.             DIM rbuf AS _MEM: rbuf = _MEMIMAGE(rhan&): roff%& = rbuf.OFFSET
  143.             DIM gbuf AS _MEM: gbuf = _MEMIMAGE(ghan&): goff%& = gbuf.OFFSET
  144.             DIM bbuf AS _MEM: bbuf = _MEMIMAGE(bhan&): boff%& = bbuf.OFFSET
  145.             '--- iterate through pixels ---
  146.             FOR y% = 0 TO shei% - 1
  147.                 FOR x% = 0 TO swid% - 1
  148.                     '--- curr/prev/next pixel offsets ---
  149.                     cpo% = x% * 4: ppo% = cpo% - 4: npo% = cpo% + 4
  150.                     '--- get pixel ARGB value from source ---
  151.                     srgb~& = _MEMGET(sbuf, soff%&, _UNSIGNED LONG)
  152.                     '--- add distributed error, shrink by 16384, clear error ---
  153.                     '--- current pixel X+0, Y+0 (= cro% (current row offset)) ---
  154.                     poff% = cro% + cpo% 'pre-calc full pixel offset
  155.                     sr% = ((srgb~& AND &HFF0000~&) \ 65536) + (_MEMGET(rbuf, roff%& + poff%, LONG) \ 16384) 'red
  156.                     sg% = ((srgb~& AND &HFF00~&) \ 256) + (_MEMGET(gbuf, goff%& + poff%, LONG) \ 16384) 'green
  157.                     sb% = (srgb~& AND &HFF~&) + (_MEMGET(bbuf, boff%& + poff%, LONG) \ 16384) 'blue
  158.                     _MEMPUT rbuf, roff%& + poff%, 0 AS LONG 'clearing each single pixel error using _MEMPUT
  159.                     _MEMPUT gbuf, goff%& + poff%, 0 AS LONG 'turns out even faster than clearing the entire
  160.                     _MEMPUT bbuf, boff%& + poff%, 0 AS LONG 'pixel row using _MEMFILL at the end of the loop
  161.                     '--- find nearest color ---
  162.                     crgb~& = _RGBA32(sr%, sg%, sb%, 0) 'used for fast value clipping + channel merge
  163.                     IF fsNearCol%(crgb~&) > 0 THEN
  164.                         npen% = fsNearCol%(crgb~&) - 1 'already known
  165.                     ELSE
  166.                         npen% = FindColor&(sr%, sg%, sb%, nhan&, 24, 255 - guiReservedPens%) 'not known, find one
  167.                         fsNearCol%(crgb~&) = npen% + 1 'save for later use
  168.                     END IF
  169.                     '--- put colormapped pixel to dest ---
  170.                     _MEMPUT nbuf, noff%&, npen% AS _UNSIGNED _BYTE
  171.                     '------------------------------------------
  172.                     '--- Floyd-Steinberg error distribution ---
  173.                     '------------------------------------------
  174.                     '--- You may comment this block out, to see the
  175.                     '--- result without applied FS matrix.
  176.                     '-----
  177.                     '--- get dest palette RGB value, calc error to clipped source ---
  178.                     nrgb~& = _PALETTECOLOR(npen%, nhan&)
  179.                     er% = ((crgb~& AND &HFF0000~&) - (nrgb~& AND &HFF0000~&)) \ 65536
  180.                     eg% = ((crgb~& AND &HFF00~&) - (nrgb~& AND &HFF00~&)) \ 256
  181.                     eb% = (crgb~& AND &HFF~&) - (nrgb~& AND &HFF~&)
  182.                     '--- distribute error according to FS matrix ---
  183.                     IF x% > 0 THEN
  184.                         '--- X-1, Y+1 (= nro% (next row offset)) ---
  185.                         poff% = nro% + ppo% 'pre-calc full pixel offset
  186.                         _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * three%) AS LONG 'red
  187.                         _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * three%) AS LONG 'green
  188.                         _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * three%) AS LONG 'blue
  189.                     END IF
  190.                     '--- X+0, Y+1 (= nro% (next row offset)) ---
  191.                     poff% = nro% + cpo% 'pre-calc full pixel offset
  192.                     _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * five%) AS LONG 'red
  193.                     _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * five%) AS LONG 'green
  194.                     _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * five%) AS LONG 'blue
  195.                     IF x% < (swid% - 1) THEN
  196.                         '--- X+1, Y+0 (= cro% (current row offset)) ---
  197.                         poff% = cro% + npo% 'pre-calc full pixel offset
  198.                         _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * seven%) AS LONG 'red
  199.                         _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * seven%) AS LONG 'green
  200.                         _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * seven%) AS LONG 'blue
  201.                         '--- X+1, Y+1 (= nro% (next row offset)) ---
  202.                         poff% = nro% + npo% 'pre-calc full pixel offset
  203.                         _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * one%) AS LONG 'red
  204.                         _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * one%) AS LONG 'green
  205.                         _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * one%) AS LONG 'blue
  206.                     END IF
  207.                     '------------------------------------------
  208.                     '--- End of FS ----------------------------
  209.                     '------------------------------------------
  210.                     noff%& = noff%& + 1 'next dest pixel
  211.                     soff%& = soff%& + 4 'next source pixel
  212.                 NEXT x%
  213.                 tmp% = cro%: cro% = nro%: nro% = tmp% 'exchange distribution array row offsets
  214.             NEXT y%
  215.             '--- memory cleanup ---
  216.             _MEMFREE bbuf
  217.             _MEMFREE gbuf
  218.             _MEMFREE rbuf
  219.             _MEMFREE nbuf
  220.             _MEMFREE sbuf
  221.             '--- set result ---
  222.             RemapImageFS& = nhan&
  223.             nhan& = -1 'avoid freeing below
  224.         END IF
  225.         $CHECKING:ON
  226.         '--- remapping done or error, cleanup remains ---
  227.         IF bhan& < -1 THEN _FREEIMAGE bhan&
  228.         IF ghan& < -1 THEN _FREEIMAGE ghan&
  229.         IF rhan& < -1 THEN _FREEIMAGE rhan&
  230.         IF nhan& < -1 THEN _FREEIMAGE nhan&
  231.         IF than& < -1 THEN _FREEIMAGE than&
  232.     END IF
  233.  

Hopefully RhoSigma won't mind me blatantly stealing from his GUI framework for the dithering, when there's more than 256 colors involved in the image.  If he's happy with the way this performs, and gives his blessing, I'll add it into the SaveImage library and it'll become another tool in it which an user might enjoy having access to.

As this works, it does 3 things for us:
First, it checks to see if the image has 256 colors or less in it.

If it does, then it checks to see if those 256 colors match the original QB64 256 color palette.  If they do, we convert the image to a standard QB64 256-color image, and at this point you can work with it with the normal color values you know and love.

If there's colors which aren't in the QB64 standard palette, then it alters the palette to match the image and then converts it to work with that palette.  (How you'd know what colors are what, I dunno, but I'll leave that up to the end user to sort out.  I suppose if you have a palette which you normally use, you could scan the colors in this one and swap them back and forth with the ones which you normally use, until the values match as originally intended.)

The results seem more than reasonable to me, and this will be a tool which I'll probably make use of quite a bit in the future.  With it, loading and using 256 color images are now available once again with QB64!


NOTE: Don't forget the attached files!



In the demo, the first pass uses the standard QB64 256 color palette.  As you notice, the white text which we print to the screen with, continues to remain white, with no issues.

The second pass uses a random set of colors, which certainly won't match the standard 256 color palette, forcing us to save the palette in use, which (more than likely) is going to change the default value of white.  The text which pops up in the top left of the converted screen is going to whatever the NEW palette tells us white is, for that image.

The third pass takes a large numbers of colors, dithers them down to 256 colors, and then saves the palette for them as closely as possible to the original.  Since we attempted to save the image, converted down using the QB64 standard palette, the colors should be the ones that you're used to seeing normally.  The white text should still look white, just as normal for us.

Play around with it.  Kick it about a bit.  See how it performs for you, and if there's any issues or problems. 

Once everyone is happy with how it works (and offers any suggestions for improvements or error checking), and, as long as RhoSigma doesn't mind me stealing his work and adding it into my library, I'll add this into SaveImage along with a means to save *.GIF format images.  (GIFs are limited to 256 colors, so we can't naturally save 32-bit files in that format, without converting them down first.)
* GuiAppFrame.h (Filesize: 11.01 KB, Downloads: 171)
Beautiful_colorful_bird_wallpaper01.jpg
* Beautiful_colorful_bird_wallpaper01.jpg (Filesize: 159.9 KB, Dimensions: 1600x1000, Views: 241)
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: Convert 32-bit images to 256 color screens
« Reply #1 on: August 12, 2019, 07:17:40 pm »
Code: QB64: [Select]
  1. DECLARE LIBRARY "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. REDIM SHARED fsNearCol%(&HFFFFFF)
  8.  
  9. ws = _NEWIMAGE(640, 480, 32) 'A 32 bit screen
  10. ts = _NEWIMAGE(640, 480, 256) 'A 256 color screen, which is only used so I can get the standard 256 color paletter from it.
  11. DIM color256 AS _UNSIGNED LONG
  12.  
  13. CONST ConvertToStandard256Palette = -1 'Change to 0 and you can see that we preseve the second pass's
  14. '                                       color information perfectly.
  15. '                                       If the CONST is set, then we convert our colors on the screen
  16. '                                       to as close of a match as possible, while preserving the standard
  17. '                                       QB64 256-color palette.
  18.  
  19. CLS , _RGB32(0, 0, 0)
  20.  
  21. FOR j = 1 TO 2
  22.     IF j = 1 THEN
  23.         FOR i = 1 TO 100 '100 random colors
  24.             'if we want to use the standard 256 color screen palette, we can do so as below
  25.             color256 = _RGB32(_RED(i, ts), _GREEN(i, ts), _BLUE(i, ts))
  26.             LINE (RND * 640, RND * 480)-(RND * 640, RND * 480), color256, BF
  27.         NEXT
  28.     ELSE 'we can go with completely random colors with the following instead:
  29.         FOR i = 1 TO 100 '100 random colors
  30.             LINE (RND * 640, RND * 480)-(RND * 640, RND * 480), &HF0000000 + RND * &HFFFFFFF, BF
  31.         NEXT
  32.     END IF
  33.     PRINT "This is the original screen, pass"; j
  34.     SLEEP 'show the original screen
  35.  
  36.     t = Image32To256(ws)
  37.     SCREEN t 'show the standard 256 image screen with the image converted over
  38.     '         this keeps us from having to learn or use any new/unique palettes the image may have
  39.     '         but, it does cause us to lose details and hues.
  40.     PRINT "This is the 256-color screen, pass"; j
  41.     SLEEP
  42.     SCREEN ws
  43.     _FREEIMAGE t
  44.     CLS
  45.  
  46.  
  47. l = _LOADIMAGE("C:\QB64 Repo\Steve64\Beautiful_colorful_bird_wallpaper01.jpg", 32)
  48. _SCREENMOVE 0, 0 'move the screen to use as much of the screen as possible, since it's so damn huge!
  49. PRINT "This is the original 32-bit screen."
  50. SLEEP 'to show the 32-bit image of the colorful bird I found
  51.  
  52. t = Image32To256(l)
  53. SCREEN t 'show the 256 image screen with the image converted over
  54. _SCREENMOVE 0, 0 'move this one too!
  55. PRINT "This is the converted 256 color screen."
  56. 'And we're done.  You should now be seeing a pretty little 256 color version of the bird
  57.  
  58.  
  59. FUNCTION Image32To256 (image&)
  60.     DIM o AS _OFFSET
  61.     DIM t AS _UNSIGNED LONG, color256 AS _UNSIGNED LONG
  62.     DIM index256 AS _UNSIGNED LONG
  63.     TYPE Pal_type
  64.         c AS _UNSIGNED LONG 'color index
  65.         n AS LONG 'number of times it appears
  66.     END TYPE
  67.     DIM Pal(255) AS _UNSIGNED LONG
  68.     I256 = _NEWIMAGE(_WIDTH(image&), _HEIGHT(image&), 256)
  69.     DIM m(1) AS _MEM: m(0) = _MEMIMAGE(image&): m(1) = _MEMIMAGE(I256)
  70.     DO 'get the palette and number of colors used
  71.         _MEMGET m(0), m(0).OFFSET + o, t 'Get the colors from the original screen
  72.         FOR i = 0 TO colors 'check to see if they're in the existing palette we're making
  73.             IF Pal(i) = t THEN EXIT FOR
  74.         NEXT
  75.         IF i > colors THEN
  76.             Pal(colors) = t
  77.             colors = colors + 1 'increment the index for the new color found
  78.             IF colors > 255 THEN 'no need to check any further; it's not a normal QB64 256 color image
  79.                 Image32To256 = RemapImageFS(image&, I256)
  80.                 _FREEIMAGE I256
  81.                 _MEMFREE m()
  82.                 EXIT FUNCTION 'and we're done, with 100% image compatability saved
  83.             END IF
  84.         END IF
  85.         o = o + 4
  86.     LOOP UNTIL o >= m(0).SIZE
  87.  
  88.     '  we might be working with a standard qb64 256 color screen
  89.     '  check for that first
  90.     colors = colors - 1 'back up one, as we found our limit and aren't needing to set another
  91.     FOR i = 0 TO colors 'comparing palette against QB64 256 color palette
  92.         t = Pal(i)
  93.         index256 = _RGBA(_RED(t), _GREEN(t), _BLUE(t), _ALPHA(t), I256)
  94.         color256 = _RGBA32(_RED(index256, I256), _GREEN(index256, I256), _BLUE(index256, I256), _ALPHA(index256, I256))
  95.         IF t <> color256 THEN NSCU = -1: EXIT FOR
  96.     NEXT
  97.     IF NSCU THEN 'it's not a standard QB64 256 color palette, but it's still less than 256 total colors.
  98.         IF ConvertToStandard256Palette THEN
  99.             TI256 = RemapImageFS(image&, I256)
  100.             _MEMFREE m(1) 'free the old memory
  101.             _FREEIMAGE I256 'and the old image
  102.             I256 = TI256 'replace with the new image
  103.             m(1) = _MEMIMAGE(I256) 'and point the mem block to the new image
  104.         ELSE
  105.             FOR i = 0 TO colors: _PALETTECOLOR i, Pal(i), I256: NEXT 'set the palette
  106.         END IF
  107.     END IF
  108.     'If we didn't change the palette above, we should work 100% with qb64's internal 256 color palette
  109.     o = 0
  110.     DO 'Get the colors, put them to a 256 color screen, as is
  111.         _MEMGET m(0), m(0).OFFSET + o + 3, a
  112.         _MEMGET m(0), m(0).OFFSET + o + 2, r
  113.         _MEMGET m(0), m(0).OFFSET + o + 1, g
  114.         _MEMGET m(0), m(0).OFFSET + o + 0, b
  115.         _MEMPUT m(1), m(1).OFFSET + o \ 4, _RGBA(r, g, b, a, I256) AS _UNSIGNED _BYTE
  116.         o = o + 4
  117.     LOOP UNTIL o >= m(0).SIZE
  118.     _MEMFREE m()
  119.     Image32To256 = I256
  120.  
  121. FUNCTION RemapImageFS& (ohan&, dhan&)
  122.     RemapImageFS& = -1 'so far return invalid handle
  123.     shan& = ohan& 'avoid side effect on given argument
  124.     IF shan& < -1 THEN
  125.         '--- check/adjust source image & get new 8-bit image ---
  126.         swid% = _WIDTH(shan&): shei% = _HEIGHT(shan&)
  127.         IF _PIXELSIZE(shan&) <> 4 THEN
  128.             than& = _NEWIMAGE(swid%, shei%, 32)
  129.             IF than& >= -1 THEN EXIT FUNCTION
  130.             _PUTIMAGE , shan&, than&
  131.             shan& = than&
  132.         ELSE
  133.             than& = -1 'avoid freeing below
  134.         END IF
  135.         nhan& = _NEWIMAGE(swid%, shei%, 256)
  136.         '--- Floyd-Steinberg error distribution arrays ---
  137.         rhan& = _NEWIMAGE(swid%, 2, 32) 'these are missused as LONG arrays,
  138.         ghan& = _NEWIMAGE(swid%, 2, 32) 'with CHECKING:OFF this is much faster
  139.         bhan& = _NEWIMAGE(swid%, 2, 32) 'than real QB64 arrays
  140.         '--- curr/next row offsets (for distribution array access) ---
  141.         cro% = 0: nro% = swid% * 4 'will be swapped after each pixel row
  142.         '--- the matrix values are extended by 16384 to avoid slow floating ---
  143.         '--- point ops and to allow for integer storage in the above arrays ---
  144.         '--- also it's a power of 2, which may be optimized into a bitshift ---
  145.         seven% = (7 / 16) * 16384 'X+1,Y+0 error fraction
  146.         three% = (3 / 16) * 16384 'X-1,Y+1 error fraction
  147.         five% = (5 / 16) * 16384 'X+0,Y+1 error fraction
  148.         one% = (1 / 16) * 16384 'X+1,Y+1 error fraction
  149.         '--- if all is good, then start remapping ---
  150.         $CHECKING:OFF
  151.         IF nhan& < -1 AND rhan& < -1 AND ghan& < -1 AND bhan& < -1 THEN
  152.             _COPYPALETTE dhan&, nhan& 'dest palette to new image
  153.             '--- for speed we do direct memory access ---
  154.             DIM sbuf AS _MEM: sbuf = _MEMIMAGE(shan&): soff%& = sbuf.OFFSET
  155.             DIM nbuf AS _MEM: nbuf = _MEMIMAGE(nhan&): noff%& = nbuf.OFFSET
  156.             DIM rbuf AS _MEM: rbuf = _MEMIMAGE(rhan&): roff%& = rbuf.OFFSET
  157.             DIM gbuf AS _MEM: gbuf = _MEMIMAGE(ghan&): goff%& = gbuf.OFFSET
  158.             DIM bbuf AS _MEM: bbuf = _MEMIMAGE(bhan&): boff%& = bbuf.OFFSET
  159.             '--- iterate through pixels ---
  160.             FOR y% = 0 TO shei% - 1
  161.                 FOR x% = 0 TO swid% - 1
  162.                     '--- curr/prev/next pixel offsets ---
  163.                     cpo% = x% * 4: ppo% = cpo% - 4: npo% = cpo% + 4
  164.                     '--- get pixel ARGB value from source ---
  165.                     srgb~& = _MEMGET(sbuf, soff%&, _UNSIGNED LONG)
  166.                     '--- add distributed error, shrink by 16384, clear error ---
  167.                     '--- current pixel X+0, Y+0 (= cro% (current row offset)) ---
  168.                     poff% = cro% + cpo% 'pre-calc full pixel offset
  169.                     sr% = ((srgb~& AND &HFF0000~&) \ 65536) + (_MEMGET(rbuf, roff%& + poff%, LONG) \ 16384) 'red
  170.                     sg% = ((srgb~& AND &HFF00~&) \ 256) + (_MEMGET(gbuf, goff%& + poff%, LONG) \ 16384) 'green
  171.                     sb% = (srgb~& AND &HFF~&) + (_MEMGET(bbuf, boff%& + poff%, LONG) \ 16384) 'blue
  172.                     _MEMPUT rbuf, roff%& + poff%, 0 AS LONG 'clearing each single pixel error using _MEMPUT
  173.                     _MEMPUT gbuf, goff%& + poff%, 0 AS LONG 'turns out even faster than clearing the entire
  174.                     _MEMPUT bbuf, boff%& + poff%, 0 AS LONG 'pixel row using _MEMFILL at the end of the loop
  175.                     '--- find nearest color ---
  176.                     crgb~& = _RGBA32(sr%, sg%, sb%, 0) 'used for fast value clipping + channel merge
  177.                     IF fsNearCol%(crgb~&) > 0 THEN
  178.                         npen% = fsNearCol%(crgb~&) - 1 'already known
  179.                     ELSE
  180.                         npen% = FindColor&(sr%, sg%, sb%, nhan&, 24, 255 - guiReservedPens%) 'not known, find one
  181.                         fsNearCol%(crgb~&) = npen% + 1 'save for later use
  182.                     END IF
  183.                     '--- put colormapped pixel to dest ---
  184.                     _MEMPUT nbuf, noff%&, npen% AS _UNSIGNED _BYTE
  185.                     '------------------------------------------
  186.                     '--- Floyd-Steinberg error distribution ---
  187.                     '------------------------------------------
  188.                     '--- You may comment this block out, to see the
  189.                     '--- result without applied FS matrix.
  190.                     '-----
  191.                     '--- get dest palette RGB value, calc error to clipped source ---
  192.                     nrgb~& = _PALETTECOLOR(npen%, nhan&)
  193.                     er% = ((crgb~& AND &HFF0000~&) - (nrgb~& AND &HFF0000~&)) \ 65536
  194.                     eg% = ((crgb~& AND &HFF00~&) - (nrgb~& AND &HFF00~&)) \ 256
  195.                     eb% = (crgb~& AND &HFF~&) - (nrgb~& AND &HFF~&)
  196.                     '--- distribute error according to FS matrix ---
  197.                     IF x% > 0 THEN
  198.                         '--- X-1, Y+1 (= nro% (next row offset)) ---
  199.                         poff% = nro% + ppo% 'pre-calc full pixel offset
  200.                         _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * three%) AS LONG 'red
  201.                         _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * three%) AS LONG 'green
  202.                         _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * three%) AS LONG 'blue
  203.                     END IF
  204.                     '--- X+0, Y+1 (= nro% (next row offset)) ---
  205.                     poff% = nro% + cpo% 'pre-calc full pixel offset
  206.                     _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * five%) AS LONG 'red
  207.                     _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * five%) AS LONG 'green
  208.                     _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * five%) AS LONG 'blue
  209.                     IF x% < (swid% - 1) THEN
  210.                         '--- X+1, Y+0 (= cro% (current row offset)) ---
  211.                         poff% = cro% + npo% 'pre-calc full pixel offset
  212.                         _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * seven%) AS LONG 'red
  213.                         _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * seven%) AS LONG 'green
  214.                         _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * seven%) AS LONG 'blue
  215.                         '--- X+1, Y+1 (= nro% (next row offset)) ---
  216.                         poff% = nro% + npo% 'pre-calc full pixel offset
  217.                         _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * one%) AS LONG 'red
  218.                         _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * one%) AS LONG 'green
  219.                         _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * one%) AS LONG 'blue
  220.                     END IF
  221.                     '------------------------------------------
  222.                     '--- End of FS ----------------------------
  223.                     '------------------------------------------
  224.                     noff%& = noff%& + 1 'next dest pixel
  225.                     soff%& = soff%& + 4 'next source pixel
  226.                 NEXT x%
  227.                 tmp% = cro%: cro% = nro%: nro% = tmp% 'exchange distribution array row offsets
  228.             NEXT y%
  229.             '--- memory cleanup ---
  230.             _MEMFREE bbuf
  231.             _MEMFREE gbuf
  232.             _MEMFREE rbuf
  233.             _MEMFREE nbuf
  234.             _MEMFREE sbuf
  235.             '--- set result ---
  236.             RemapImageFS& = nhan&
  237.             nhan& = -1 'avoid freeing below
  238.         END IF
  239.         $CHECKING:ON
  240.         '--- remapping done or error, cleanup remains ---
  241.         IF bhan& < -1 THEN _FREEIMAGE bhan&
  242.         IF ghan& < -1 THEN _FREEIMAGE ghan&
  243.         IF rhan& < -1 THEN _FREEIMAGE rhan&
  244.         IF nhan& < -1 THEN _FREEIMAGE nhan&
  245.         IF than& < -1 THEN _FREEIMAGE than&
  246.     END IF

A slightly upgraded version.

This adds a CONST which we can insert into our code to alter behavior in the case of the 2nd pass above (less than 256 colors, but they're not in the standard color palette).

CONST ConvertToStandard256Palette = -1 'Change to 0 and you can see that we preseve the second pass's
'                                       color information perfectly.
'                                       If the CONST is set, then we convert our colors on the screen
'                                       to as close of a match as possible, while preserving the standard
'                                       QB64 256-color palette.

Set the CONST and you can convert the screen image to use the normal 256 color palette (which will alter the colors some, but we shoot for the closest matches possible, since they're non-standard).  Leave it alone, or set it to 0, and you convert the screen from 32-bit to 256 colors, preserving the information perfectly, though it will change the palette which is associated with it (which is what you'd probably want to do if you were going to save the image for use in another program later). 

It gives us the best of both worlds -- Preserve the data without any changes, except to the palette which we're used to working with in QB64...  OR  ... Change the data to be as close of a match as possible to QB64's standard palette, and then preserve it, so we can use the color values we normally work with.

https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
    • View Profile
Re: Convert 32-bit images to 256 color screens
« Reply #2 on: August 13, 2019, 03:57:42 am »
Good work Steve, just go ahead, I've no problem with 3rd party use of my stuff.

However, did you think about my suggestion to modifiy the code to waive to the DECLARE LIBRARY and fsNearCol%() array?
https://qb64forum.alephc.xyz/index.php?topic=1606.msg108204#msg108204
« Last Edit: January 31, 2022, 06:34:29 pm 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 RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
    • View Profile
Re: Convert 32-bit images to 256 color screens
« Reply #3 on: August 13, 2019, 06:27:27 am »
Here's effectively what I mean. No need for the c headerfile either anymore.

Code: QB64: [Select]
  1.      
  2.     ws = _NEWIMAGE(640, 480, 32) 'A 32 bit screen
  3.     ts = _NEWIMAGE(640, 480, 256) 'A 256 color screen, which is only used so I can get the standard 256 color paletter from it.
  4.     SCREEN ws
  5.     DIM color256 AS _UNSIGNED LONG
  6.      
  7.     CONST ConvertToStandard256Palette = -1 'Change to 0 and you can see that we preseve the second pass's
  8.     '                                       color information perfectly.
  9.     '                                       If the CONST is set, then we convert our colors on the screen
  10.     '                                       to as close of a match as possible, while preserving the standard
  11.     '                                       QB64 256-color palette.
  12.      
  13.     CLS , _RGB32(0, 0, 0)
  14.      
  15.     FOR j = 1 TO 2
  16.         IF j = 1 THEN
  17.             FOR i = 1 TO 100 '100 random colors
  18.                 'if we want to use the standard 256 color screen palette, we can do so as below
  19.                 color256 = _RGB32(_RED(i, ts), _GREEN(i, ts), _BLUE(i, ts))
  20.                 LINE (RND * 640, RND * 480)-(RND * 640, RND * 480), color256, BF
  21.             NEXT
  22.         ELSE 'we can go with completely random colors with the following instead:
  23.             FOR i = 1 TO 100 '100 random colors
  24.                 LINE (RND * 640, RND * 480)-(RND * 640, RND * 480), &HF0000000 + RND * &HFFFFFFF, BF
  25.             NEXT
  26.         END IF
  27.         PRINT "This is the original screen, pass"; j
  28.         SLEEP 'show the original screen
  29.      
  30.         t = Image32To256(ws)
  31.         SCREEN t 'show the standard 256 image screen with the image converted over
  32.         '         this keeps us from having to learn or use any new/unique palettes the image may have
  33.         '         but, it does cause us to lose details and hues.
  34.         PRINT "This is the 256-color screen, pass"; j
  35.         SLEEP
  36.         SCREEN ws
  37.         _FREEIMAGE t
  38.         CLS
  39.     NEXT
  40.      
  41.      
  42.     l = _LOADIMAGE("C:\QB64 Repo\Steve64\Beautiful_colorful_bird_wallpaper01.jpg", 32)
  43.     SCREEN l
  44.     _SCREENMOVE 0, 0 'move the screen to use as much of the screen as possible, since it's so damn huge!
  45.     PRINT "This is the original 32-bit screen."
  46.     SLEEP 'to show the 32-bit image of the colorful bird I found
  47.      
  48.     t = Image32To256(l)
  49.     SCREEN t 'show the 256 image screen with the image converted over
  50.     _SCREENMOVE 0, 0 'move this one too!
  51.     PRINT "This is the converted 256 color screen."
  52.     'And we're done.  You should now be seeing a pretty little 256 color version of the bird
  53.      
  54.      
  55.     FUNCTION Image32To256 (image&)
  56.         DIM o AS _OFFSET
  57.         DIM t AS _UNSIGNED LONG, color256 AS _UNSIGNED LONG
  58.         DIM index256 AS _UNSIGNED LONG
  59.         TYPE Pal_type
  60.             c AS _UNSIGNED LONG 'color index
  61.             n AS LONG 'number of times it appears
  62.         END TYPE
  63.         DIM Pal(255) AS _UNSIGNED LONG
  64.         I256 = _NEWIMAGE(_WIDTH(image&), _HEIGHT(image&), 256)
  65.         DIM m(1) AS _MEM: m(0) = _MEMIMAGE(image&): m(1) = _MEMIMAGE(I256)
  66.         DO 'get the palette and number of colors used
  67.             _MEMGET m(0), m(0).OFFSET + o, t 'Get the colors from the original screen
  68.             FOR i = 0 TO colors 'check to see if they're in the existing palette we're making
  69.                 IF Pal(i) = t THEN EXIT FOR
  70.             NEXT
  71.             IF i > colors THEN
  72.                 Pal(colors) = t
  73.                 colors = colors + 1 'increment the index for the new color found
  74.                 IF colors > 255 THEN 'no need to check any further; it's not a normal QB64 256 color image
  75.                     Image32To256 = RemapImageFS(image&, I256)
  76.                     _FREEIMAGE I256
  77.                     _MEMFREE m()
  78.                     EXIT FUNCTION 'and we're done, with 100% image compatability saved
  79.                 END IF
  80.             END IF
  81.             o = o + 4
  82.         LOOP UNTIL o >= m(0).SIZE
  83.      
  84.         '  we might be working with a standard qb64 256 color screen
  85.         '  check for that first
  86.         colors = colors - 1 'back up one, as we found our limit and aren't needing to set another
  87.         FOR i = 0 TO colors 'comparing palette against QB64 256 color palette
  88.             t = Pal(i)
  89.             index256 = _RGBA(_RED(t), _GREEN(t), _BLUE(t), _ALPHA(t), I256)
  90.             color256 = _RGBA32(_RED(index256, I256), _GREEN(index256, I256), _BLUE(index256, I256), _ALPHA(index256, I256))
  91.             IF t <> color256 THEN NSCU = -1: EXIT FOR
  92.         NEXT
  93.         IF NSCU THEN 'it's not a standard QB64 256 color palette, but it's still less than 256 total colors.
  94.             IF ConvertToStandard256Palette THEN
  95.                 TI256 = RemapImageFS(image&, I256)
  96.                 _MEMFREE m(1) 'free the old memory
  97.                 _FREEIMAGE I256 'and the old image
  98.                 I256 = TI256 'replace with the new image
  99.                 m(1) = _MEMIMAGE(I256) 'and point the mem block to the new image
  100.             ELSE
  101.                 FOR i = 0 TO colors: _PALETTECOLOR i, Pal(i), I256: NEXT 'set the palette
  102.             END IF
  103.         END IF
  104.         'If we didn't change the palette above, we should work 100% with qb64's internal 256 color palette
  105.         o = 0
  106.         DO 'Get the colors, put them to a 256 color screen, as is
  107.             _MEMGET m(0), m(0).OFFSET + o + 3, a
  108.             _MEMGET m(0), m(0).OFFSET + o + 2, r
  109.             _MEMGET m(0), m(0).OFFSET + o + 1, g
  110.             _MEMGET m(0), m(0).OFFSET + o + 0, b
  111.             _MEMPUT m(1), m(1).OFFSET + o \ 4, _RGBA(r, g, b, a, I256) AS _UNSIGNED _BYTE
  112.             o = o + 4
  113.         LOOP UNTIL o >= m(0).SIZE
  114.         _MEMFREE m()
  115.         Image32To256 = I256
  116.      
  117.     FUNCTION RemapImageFS& (ohan&, dhan&)
  118.         RemapImageFS& = -1 'so far return invalid handle
  119.         shan& = ohan& 'avoid side effect on given argument
  120.         IF shan& < -1 THEN
  121.             '--- check/adjust source image & get new 8-bit image ---
  122.             swid% = _WIDTH(shan&): shei% = _HEIGHT(shan&)
  123.             IF _PIXELSIZE(shan&) <> 4 THEN
  124.                 than& = _NEWIMAGE(swid%, shei%, 32)
  125.                 IF than& >= -1 THEN EXIT FUNCTION
  126.                 _PUTIMAGE , shan&, than&
  127.                 shan& = than&
  128.             ELSE
  129.                 than& = -1 'avoid freeing below
  130.             END IF
  131.             nhan& = _NEWIMAGE(swid%, shei%, 256)
  132.             '--- Floyd-Steinberg error distribution arrays ---
  133.             rhan& = _NEWIMAGE(swid%, 2, 32) 'these are missused as LONG arrays,
  134.             ghan& = _NEWIMAGE(swid%, 2, 32) 'with CHECKING:OFF this is much faster
  135.             bhan& = _NEWIMAGE(swid%, 2, 32) 'than real QB64 arrays
  136.             '--- curr/next row offsets (for distribution array access) ---
  137.             cro% = 0: nro% = swid% * 4 'will be swapped after each pixel row
  138.             '--- the matrix values are extended by 16384 to avoid slow floating ---
  139.             '--- point ops and to allow for integer storage in the above arrays ---
  140.             '--- also it's a power of 2, which may be optimized into a bitshift ---
  141.             seven% = (7 / 16) * 16384 'X+1,Y+0 error fraction
  142.             three% = (3 / 16) * 16384 'X-1,Y+1 error fraction
  143.             five% = (5 / 16) * 16384 'X+0,Y+1 error fraction
  144.             one% = (1 / 16) * 16384 'X+1,Y+1 error fraction
  145.             '--- if all is good, then start remapping ---
  146.             $CHECKING:OFF
  147.             IF nhan& < -1 AND rhan& < -1 AND ghan& < -1 AND bhan& < -1 THEN
  148.                 _COPYPALETTE dhan&, nhan& 'dest palette to new image
  149.                 '--- for speed we do direct memory access ---
  150.                 DIM sbuf AS _MEM: sbuf = _MEMIMAGE(shan&): soff%& = sbuf.OFFSET
  151.                 DIM nbuf AS _MEM: nbuf = _MEMIMAGE(nhan&): noff%& = nbuf.OFFSET
  152.                 DIM rbuf AS _MEM: rbuf = _MEMIMAGE(rhan&): roff%& = rbuf.OFFSET
  153.                 DIM gbuf AS _MEM: gbuf = _MEMIMAGE(ghan&): goff%& = gbuf.OFFSET
  154.                 DIM bbuf AS _MEM: bbuf = _MEMIMAGE(bhan&): boff%& = bbuf.OFFSET
  155.                 '--- iterate through pixels ---
  156.                 FOR y% = 0 TO shei% - 1
  157.                     FOR x% = 0 TO swid% - 1
  158.                         '--- curr/prev/next pixel offsets ---
  159.                         cpo% = x% * 4: ppo% = cpo% - 4: npo% = cpo% + 4
  160.                         '--- get pixel ARGB value from source ---
  161.                         srgb~& = _MEMGET(sbuf, soff%&, _UNSIGNED LONG)
  162.                         '--- add distributed error, shrink by 16384, clear error ---
  163.                         '--- current pixel X+0, Y+0 (= cro% (current row offset)) ---
  164.                         poff% = cro% + cpo% 'pre-calc full pixel offset
  165.                         sr% = ((srgb~& AND &HFF0000~&) \ 65536) + (_MEMGET(rbuf, roff%& + poff%, LONG) \ 16384) 'red
  166.                         sg% = ((srgb~& AND &HFF00~&) \ 256) + (_MEMGET(gbuf, goff%& + poff%, LONG) \ 16384) 'green
  167.                         sb% = (srgb~& AND &HFF~&) + (_MEMGET(bbuf, boff%& + poff%, LONG) \ 16384) 'blue
  168.                         _MEMPUT rbuf, roff%& + poff%, 0 AS LONG 'clearing each single pixel error using _MEMPUT
  169.                         _MEMPUT gbuf, goff%& + poff%, 0 AS LONG 'turns out even faster than clearing the entire
  170.                         _MEMPUT bbuf, boff%& + poff%, 0 AS LONG 'pixel row using _MEMFILL at the end of the loop
  171.                         '--- find nearest color ---
  172.                         crgb~& = _RGBA32(sr%, sg%, sb%, 0) 'used for fast value clipping + channel merge
  173.                         npen% = _RGB(sr%, sg%, sb%, nhan&)
  174.                         '--- put colormapped pixel to dest ---
  175.                         _MEMPUT nbuf, noff%&, npen% AS _UNSIGNED _BYTE
  176.                         '------------------------------------------
  177.                         '--- Floyd-Steinberg error distribution ---
  178.                         '------------------------------------------
  179.                         '--- You may comment this block out, to see the
  180.                         '--- result without applied FS matrix.
  181.                         '-----
  182.                         '--- get dest palette RGB value, calc error to clipped source ---
  183.                         nrgb~& = _PALETTECOLOR(npen%, nhan&)
  184.                         er% = ((crgb~& AND &HFF0000~&) - (nrgb~& AND &HFF0000~&)) \ 65536
  185.                         eg% = ((crgb~& AND &HFF00~&) - (nrgb~& AND &HFF00~&)) \ 256
  186.                         eb% = (crgb~& AND &HFF~&) - (nrgb~& AND &HFF~&)
  187.                         '--- distribute error according to FS matrix ---
  188.                         IF x% > 0 THEN
  189.                             '--- X-1, Y+1 (= nro% (next row offset)) ---
  190.                             poff% = nro% + ppo% 'pre-calc full pixel offset
  191.                             _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * three%) AS LONG 'red
  192.                             _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * three%) AS LONG 'green
  193.                             _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * three%) AS LONG 'blue
  194.                         END IF
  195.                         '--- X+0, Y+1 (= nro% (next row offset)) ---
  196.                         poff% = nro% + cpo% 'pre-calc full pixel offset
  197.                         _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * five%) AS LONG 'red
  198.                         _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * five%) AS LONG 'green
  199.                         _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * five%) AS LONG 'blue
  200.                         IF x% < (swid% - 1) THEN
  201.                             '--- X+1, Y+0 (= cro% (current row offset)) ---
  202.                             poff% = cro% + npo% 'pre-calc full pixel offset
  203.                             _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * seven%) AS LONG 'red
  204.                             _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * seven%) AS LONG 'green
  205.                             _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * seven%) AS LONG 'blue
  206.                             '--- X+1, Y+1 (= nro% (next row offset)) ---
  207.                             poff% = nro% + npo% 'pre-calc full pixel offset
  208.                             _MEMPUT rbuf, roff%& + poff%, _MEMGET(rbuf, roff%& + poff%, LONG) + (er% * one%) AS LONG 'red
  209.                             _MEMPUT gbuf, goff%& + poff%, _MEMGET(gbuf, goff%& + poff%, LONG) + (eg% * one%) AS LONG 'green
  210.                             _MEMPUT bbuf, boff%& + poff%, _MEMGET(bbuf, boff%& + poff%, LONG) + (eb% * one%) AS LONG 'blue
  211.                         END IF
  212.                         '------------------------------------------
  213.                         '--- End of FS ----------------------------
  214.                         '------------------------------------------
  215.                         noff%& = noff%& + 1 'next dest pixel
  216.                         soff%& = soff%& + 4 'next source pixel
  217.                     NEXT x%
  218.                     tmp% = cro%: cro% = nro%: nro% = tmp% 'exchange distribution array row offsets
  219.                 NEXT y%
  220.                 '--- memory cleanup ---
  221.                 _MEMFREE bbuf
  222.                 _MEMFREE gbuf
  223.                 _MEMFREE rbuf
  224.                 _MEMFREE nbuf
  225.                 _MEMFREE sbuf
  226.                 '--- set result ---
  227.                 RemapImageFS& = nhan&
  228.                 nhan& = -1 'avoid freeing below
  229.             END IF
  230.             $CHECKING:ON
  231.             '--- remapping done or error, cleanup remains ---
  232.             IF bhan& < -1 THEN _FREEIMAGE bhan&
  233.             IF ghan& < -1 THEN _FREEIMAGE ghan&
  234.             IF rhan& < -1 THEN _FREEIMAGE rhan&
  235.             IF nhan& < -1 THEN _FREEIMAGE nhan&
  236.             IF than& < -1 THEN _FREEIMAGE than&
  237.         END IF
« Last Edit: August 13, 2019, 06:29:04 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