Jump to content

Trouble with Floyd-Steinberg dithering


Go to solution Solved by AndyG,

Recommended Posts

I'm trying to make a 1bit (two colors) floyd-steinberg dithering on images but I'm getting artifacts/blemishes, I have tried hundreds of permutations so now I have to ask you guys if you can spot the mistake(s).

Pseudo code from Wikipedia... https://en.wikipedia.org/wiki/Floyd–Steinberg_dithering

Quote

for each y from top to bottom
for each x from left to right
  oldpixel  := pixel[x][y]
  newpixel  := find_closest_palette_color(oldpixel)
  pixel[x][y]  := newpixel
  quant_error  := oldpixel - newpixel
  pixel[x+1][y  ] := pixel[x+1][y  ] + 7/16 * quant_error
  pixel[x-1][y+1] := pixel[x-1][y+1] + 3/16 * quant_error
  pixel[x  ][y+1] := pixel[x  ][y+1] + 5/16 * quant_error
  pixel[x+1][y+1] := pixel[x+1][y+1] + 1/16 * quant_error

  find_closest_palette_color(oldpixel) = round(oldpixel / 255)

 

My code...

#include<GDIPlus.au3>

HotKeySet("{ESC}", "_exit")

_GDIPlus_Startup()

Global $Image = _GDIPlus_BitmapCreateFromFile  ("graytest.png")
   
Global $Width = _GDIPlus_ImageGetWidth($Image), $Height = _GDIPlus_ImageGetHeight($Image)

$Gui= GUICreate("Floyd-Steinberg Dithering", $Width, $Height)
      GUISetState()
      
$Graphics = _GDIPlus_GraphicsCreateFromHWND($Gui)

            _GDIPlus_GraphicsDrawImageRect ($Graphics,$Image, 0, 0, $Width, $Height)

For $y = 0 To $Height - 1
   For $x = 0 To $Width - 1
       
       $oldpixel = Dec(Hex(_GDIPlus_BitmapGetPixel($Image, $X, $Y), 2))
       $newpixel = Round($oldpixel / 255) ? 0 : 1;
                   _GDIPlus_BitmapSetPixel($Image, $X, $Y, String("0xFF" & Hex($newpixel*255, 2) & Hex($newpixel*255, 2) & Hex($newpixel*255, 2)))
       $quant_error =  $oldpixel - $newpixel

;~ Consolewrite($quant_error & " " & @crlf)

;-------Floyd-Steinberg
  _GDIPlus_BitmapSetPixel($Image, $X+1, $Y,   _GDIPlus_BitmapGetPixel($Image, $X+1, $Y  ) + 7/16 * $quant_error)
  _GDIPlus_BitmapSetPixel($Image, $X-1, $Y+1, _GDIPlus_BitmapGetPixel($Image, $X-1, $Y+1) + 3/16 * $quant_error)
  _GDIPlus_BitmapSetPixel($Image, $X,   $Y+1, _GDIPlus_BitmapGetPixel($Image, $X,   $Y+1) + 5/16 * $quant_error)
  _GDIPlus_BitmapSetPixel($Image, $X+1, $Y+1, _GDIPlus_BitmapGetPixel($Image, $X+1, $Y+1) + 1/16 * $quant_error)

;-------Sierra2
;~   _GDIPlus_BitmapSetPixel($Image, $X+1, $Y  , _GDIPlus_BitmapGetPixel($Image, $X+1, $Y  ) + 4/16 * $quant_error)
;~   _GDIPlus_BitmapSetPixel($Image, $X+2, $Y  , _GDIPlus_BitmapGetPixel($Image, $X+2, $Y  ) + 3/16 * $quant_error)
;~   _GDIPlus_BitmapSetPixel($Image, $X-1, $Y+1, _GDIPlus_BitmapGetPixel($Image, $X-1, $Y+1) + 1/16 * $quant_error)
;~   _GDIPlus_BitmapSetPixel($Image, $X,   $Y+1, _GDIPlus_BitmapGetPixel($Image, $X,   $Y+1) + 2/16 * $quant_error)
;~   _GDIPlus_BitmapSetPixel($Image, $X+1, $Y+1, _GDIPlus_BitmapGetPixel($Image, $X+1, $Y+1) + 3/16 * $quant_error)
;~   _GDIPlus_BitmapSetPixel($Image, $X+2, $Y+1, _GDIPlus_BitmapGetPixel($Image, $X+2, $Y+1) + 2/16 * $quant_error)
;~   _GDIPlus_BitmapSetPixel($Image, $X+3, $Y+1, _GDIPlus_BitmapGetPixel($Image, $X+3, $Y+1) + 1/16 * $quant_error)

   Next
               _GDIPlus_GraphicsDrawImageRect($Graphics, $Image, 0, 0, $Width, $Height); to see the errors quick instead of waiting for the whole image

Next

_GDIPlus_GraphicsDrawImageRect($Graphics, $Image, 0, 0, $Width, $Height)


While GUIGetMsg <> - 3
    Sleep(10)
WEnd

Func _exit()
    Exit
EndFunc

;--------------------------------------------------------------------------------------------------------------
;~ from Wikipedia article https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering
;~ for each y from top to bottom
;~ for each x from left to right
;~   oldpixel  := pixel[x][y]
;~   newpixel  := find_closest_palette_color(oldpixel)
;~   pixel[x][y]  := newpixel
;~   quant_error  := oldpixel - newpixel
;~   pixel[x+1][y  ] := pixel[x+1][y  ] + 7/16 * quant_error
;~   pixel[x-1][y+1] := pixel[x-1][y+1] + 3/16 * quant_error
;~   pixel[x  ][y+1] := pixel[x  ][y+1] + 5/16 * quant_error
;~   pixel[x+1][y+1] := pixel[x+1][y+1] + 1/16 * quant_error

;~   find_closest_palette_color(oldpixel) = round(oldpixel / 255)
;--------------------------------------------------------------------------------------------------------------

 

If you comment the floyd-steinberg lines and uncomment the Sierra2 lines you can see that's much better without artifacts in the middle of the images, though it still has artifacts on the left side of the image, but it's mostly floyd I need working, any help?

(dont mind the slow  gdi+, I'll be using struct or c-dll when/if i get it working.)

 

/edit, forgot the pic, but try with any pic

/edit2, I will only be feeding it 8bit grayscale images)

graytest.png

Edited by Werty

Some guy's script + some other guy's script = my script!

Link to comment
Share on other sites

I should ofcourse have posted some comparison images, it's supposed to look something like this (from paintshop pro)...

And another example, first pic is the original, second made in paintshop pro, third my result, easy to spot the artifacts mine has.

 

lenadithered.png

dithercompare.png

Some guy's script + some other guy's script = my script!

Link to comment
Share on other sites

with the help of copilot I got this far, the result is better but it is blue.
I couldn't understand why.
maybe you'll get an edge

; https:
;----------------------------------------------------------------------------------------
#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7
#include <GDIPlus.au3>
#include <Math.au3>
#include <Color.au3>

; Start GDI+ engine
_GDIPlus_Startup()

; Load the image
Global $hImage = _GDIPlus_ImageLoadFromFile(@ScriptDir & "\graytest.png")

; Get image dimensions
Global $iWidth = _GDIPlus_ImageGetWidth($hImage)
Global $iHeight = _GDIPlus_ImageGetHeight($hImage)

Global $Gui = GUICreate("Floyd-Steinberg Dithering", $iWidth, $iHeight)
GUISetState()


_Floyd_Steinberg_dithering()


;**********************************
While 1
    Switch GUIGetMsg()
        Case -3 ;$GUI_EVENT_CLOSE
            ExitLoop
    EndSwitch
    Sleep(10)
WEnd
;**********************************

;--------------------------------------------------------------------------------------------------------------------------------
Func _Floyd_Steinberg_dithering()
    Local $Graphics = _GDIPlus_GraphicsCreateFromHWND($Gui)

    _GDIPlus_GraphicsDrawImageRect($Graphics, $hImage, 0, 0, $iWidth, $iHeight)
    Local $iOldColor, $iOldGray, $iNewColor, $iError
    ; Apply Floyd–Steinberg dithering
    For $y = 0 To $iHeight - 1
        For $x = 0 To $iWidth - 1
            ; Get the current pixel's color
            $iOldColor = _GDIPlus_BitmapGetPixel($hImage, $x, $y)
            $iOldGray = _ColorToGray($iOldColor)

            ; Find the closest color (black or white)
            $iNewColor = BitOR((($iOldGray < 128) ? 0xFF000000 : 0xFFFFFFFF), 0xFF)

            _GDIPlus_BitmapSetPixel($hImage, $x, $y, $iNewColor)

            ; Calculate the quantization error
            $iError = $iOldGray - _ColorToGray($iNewColor)

            ; Distribute the error to the neighboring pixels
            _DistributeError($hImage, $x + 1, $y, $iError * 7 / 16)
            _DistributeError($hImage, $x - 1, $y + 1, $iError * 3 / 16)
            _DistributeError($hImage, $x, $y + 1, $iError * 5 / 16)
            _DistributeError($hImage, $x + 1, $y + 1, $iError * 1 / 16)
        Next
    Next

    _GDIPlus_GraphicsDrawImageRect($Graphics, $hImage, 0, 0, $iWidth, $iHeight)

    ; Save the dithered image
    _GDIPlus_ImageSaveToFile($hImage, @ScriptDir & "\graytest_fs.png")

    ; Clean up resources
    _GDIPlus_BitmapDispose($hImage)
    _GDIPlus_Shutdown()

EndFunc   ;==>_Floyd_Steinberg_dithering
;--------------------------------------------------------------------------------------------------------------------------------
Func _ColorToGray($iColor)
    ; Extract RGB components
    Local $iR = BitAND(BitShift($iColor, 16), 0xFF)
    Local $iG = BitAND(BitShift($iColor, 8), 0xFF)
    Local $iB = BitAND($iColor, 0xFF)

    ; Return the grayscale value
    Return ($iR + $iG + $iB) / 3
EndFunc   ;==>_ColorToGray
;--------------------------------------------------------------------------------------------------------------------------------
Func _DistributeError($hBitmap, $x, $y, $iError)
    ; Ensure the coordinates are within the image bounds
    If $x < 0 Or $x >= _GDIPlus_ImageGetWidth($hBitmap) Or $y < 0 Or $y >= _GDIPlus_ImageGetHeight($hBitmap) Then Return

    ; Get the current pixel's color
    Local $iCurrentColor = _GDIPlus_BitmapGetPixel($hBitmap, $x, $y)
    Local $iCurrentGray = _ColorToGray($iCurrentColor)

    ; Add the error to the current pixel's gray value
    Local $iNewGray = $iCurrentGray + $iError

    ; Clamp the value between 0 and 255
    $iNewGray = _Max(0, _Min(255, $iNewGray))

    Local $aColor[] = [$iNewGray, $iNewGray, $iNewGray]

    ; Set the new pixel color
    Local $iNewColor = _ColorSetRGB($aColor)

    _GDIPlus_BitmapSetPixel($hBitmap, $x, $y, $iNewColor)
EndFunc   ;==>_DistributeError
;--------------------------------------------------------------------------------------------------------------------------------

 

Edited by ioa747
packing

I know that I know nothing

Link to comment
Share on other sites

Thanks but sorry to say that it's actually much worse than mine, there are too many large areas that are blank, i need it evenly distributed as in the sample images i posted.

I've been trying for days now, but not giving up, maybe I should try separating it all into funcs as in your code, though as i wrote earlier, I will only be feeding it 8bit grayscale so no need for the 24bit support your code has.

I'll examine your script more thoroughly, I already tried clamping but will again, and look into the error distribution thingie.

But please, anyone. :)

46 minutes ago, ioa747 said:

I couldn't understand why.

try adding a consolewrite below the iNewColor and you will see why, copilots code doesnt work, or atleast is faulty.  ;)

; Set the new pixel color
    $iNewColor = _ColorSetRGB($aColor)
consolewrite(Hex($iNewcolor, 8) & @crlf)
    _GDIPlus_BitmapSetPixel($hBitmap, $x, $y, $iNewColor)
EndFunc   ;==>_DistributeError

 

Some guy's script + some other guy's script = my script!

Link to comment
Share on other sites

  • Solution
Posted (edited)

Hi,

the reason for these "artefacts" is the accumulation of all rounding errors caused by the use of byte "colours" aka AARRGGBB.

You will get better results when you calculate only with floating numbers.

Unfortunately AutoIt is not able to "cast" a hex value into a float number (or i cannot remember it ^^), DEC() is able to cast hex into double but double is 64bit and does`nt fit into a 32bit "Pixel" AARRGGBB

First to do is to store "floats" instead of AARRGGBB within the whole image

Get the pixel value via _GDIPlus_BitmapGetPixel(), write this value into a dword struct which is on the same address as a float struct and read the dword (aka float)

$floatstruct = DllStructCreate("float")
$dwordstruct = DllStructCreate("dword", DllStructGetPtr($floatstruct))

;AARRGGBB to float
For $y = 0 To $Height - 1
    For $x = 0 To $Width - 1
        $oldPixel = Dec(Hex(_GDIPlus_BitmapGetPixel($Image, $x, $y), 2)) / 255 ;get float number between 0 and 1
        DllStructSetData($floatstruct, 1, $oldPixel)        ;write into float struct
        $oldPixel = DllStructGetData($dwordstruct, 1)       ;read as dword (aka "pixel" AARRGGBB)
        _GDIPlus_BitmapSetPixel($Image, $x, $y, $oldPixel)  ;store the float number as "pixel" AARRGGBB
    Next
Next

Now the "image" is an array of floats. 

In the next step you have to read the floating point numbers (aka oldpixel) via _GDIPlus_BitmapGetPixel(), calculate if more or less than 0.5 and set the newpixel value and set the the black or white pixel in the image and the quant_error

For $y = 0 To $Height - 1
    For $x = 0 To $Width - 1
        $oldPixel = _GDIPlus_BitmapGetPixel($Image, $x, $y) ;pixel as dword
        DllStructSetData($dwordstruct, 1, $oldPixel)        ;write into dwordstruct (place of floatstruct)
        $oldPixel = DllStructGetData($floatstruct, 1)       ;read float

        If $oldPixel <= 0.5 Then
            $newpixel = 0
        Else
            $newpixel = 1
        EndIf
        _GDIPlus_BitmapSetPixel($Image, $x, $y, String("0xFF" & Hex($newpixel * 255, 2) & Hex($newpixel * 255, 2) & Hex($newpixel * 255, 2)))

        $quant_error = $oldPixel - $newpixel

Now Floyd-Steinberg:

As mentioned before, other languages can handle images and floating point numbers, I transferred dwords and floats via DllStructs.

;-------Floyd-Steinberg
        $pixel = _GDIPlus_BitmapGetPixel($Image, $x + 1, $y);get pixel integer/DWORD AARRGGBB
        DllStructSetData($dwordstruct, 1, $pixel)           ;set into DWORD struct
        $col = DllStructGetData($floatstruct, 1)            ;read float from DWORD
        $float = $col + (7 / 16 * $quant_error)             ;calculate with float
        DllStructSetData($floatstruct, 1, $float)           ;write into float struct
        $pixel = DllStructGetData($dwordstruct, 1)          ;get dword from float and
        _GDIPlus_BitmapSetPixel($Image, $x + 1, $y, $pixel) ;write the "float" as a "pixel"

which leads to the script:

;$aligncomment=60
#include <GDIPlus.au3>

HotKeySet("{ESC}", "_exit")

_GDIPlus_Startup()

Global $Image = _GDIPlus_BitmapCreateFromFile("graytest.png")
;~ Global $Image = _GDIPlus_BitmapCreateFromFile("dithercompare.png")
;~ Global $Image = _GDIPlus_BitmapCreateFromFile("test50.png")

Global $Width = _GDIPlus_ImageGetWidth($Image), $Height = _GDIPlus_ImageGetHeight($Image)
ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $Height = ' & $Height & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console
ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $Width = ' & $Width & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console

$Gui = GUICreate("Floyd-Steinberg Dithering", $Width, $Height)
GUISetState()

$Graphics = _GDIPlus_GraphicsCreateFromHWND($Gui)

_GDIPlus_GraphicsDrawImageRect($Graphics, $Image, 0, 0, $Width, $Height)

$floatstruct = DllStructCreate("float")
$dwordstruct = DllStructCreate("dword", DllStructGetPtr($floatstruct))

;AARRGGBB to float
For $y = 0 To $Height - 1
    For $x = 0 To $Width - 1
        $oldPixel = Dec(Hex(_GDIPlus_BitmapGetPixel($Image, $x, $y), 2)) / 255 ;get float number between 0 and 1
        DllStructSetData($floatstruct, 1, $oldPixel)        ;write into float struct
        $oldPixel = DllStructGetData($dwordstruct, 1)       ;read as dword (aka "pixel" AARRGGBB)
        _GDIPlus_BitmapSetPixel($Image, $x, $y, $oldPixel)  ;store the float number as "pixel" AARRGGBB
    Next
Next
_GDIPlus_GraphicsDrawImageRect($Graphics, $Image, 0, 0, $Width, $Height) ;show image with "floats"


For $y = 0 To $Height - 1
    For $x = 0 To $Width - 1
        $oldPixel = _GDIPlus_BitmapGetPixel($Image, $x, $y) ;pixel as dword
        DllStructSetData($dwordstruct, 1, $oldPixel)        ;write into dwordstruct (place of floatstruct)
        $oldPixel = DllStructGetData($floatstruct, 1)       ;read float

        If $oldPixel <= 0.5 Then
            $newpixel = 0
        Else
            $newpixel = 1
        EndIf
        _GDIPlus_BitmapSetPixel($Image, $x, $y, String("0xFF" & Hex($newpixel * 255, 2) & Hex($newpixel * 255, 2) & Hex($newpixel * 255, 2)))

        $quant_error = $oldPixel - $newpixel

        ;-------Floyd-Steinberg
        $pixel = _GDIPlus_BitmapGetPixel($Image, $x + 1, $y);get pixel integer/DWORD AARRGGBB
        DllStructSetData($dwordstruct, 1, $pixel)           ;set into DWORD struct
        $col = DllStructGetData($floatstruct, 1)            ;read float from DWORD
        $float = $col + (7 / 16 * $quant_error)             ;calculate with float
        DllStructSetData($floatstruct, 1, $float)           ;write into float struct
        $pixel = DllStructGetData($dwordstruct, 1)          ;get dword from float and
        _GDIPlus_BitmapSetPixel($Image, $x + 1, $y, $pixel) ;write the "float" as a "pixel"


        $pixel = _GDIPlus_BitmapGetPixel($Image, $x - 1, $y + 1)
        DllStructSetData($dwordstruct, 1, $pixel)           ;set into DWORD struct
        $col = DllStructGetData($floatstruct, 1)            ;read float from DWORD
        $float = $col + (3 / 16 * $quant_error)             ;calculate with float
        DllStructSetData($floatstruct, 1, $float)           ;write into float struct
        $pixel = DllStructGetData($dwordstruct, 1)          ;get dword from float and
        _GDIPlus_BitmapSetPixel($Image, $x - 1, $y + 1, $pixel)


        $pixel = _GDIPlus_BitmapGetPixel($Image, $x, $y + 1)
        DllStructSetData($dwordstruct, 1, $pixel)           ;set into DWORD struct
        $col = DllStructGetData($floatstruct, 1)            ;read float from DWORD
        $float = $col + (5 / 16 * $quant_error)             ;calculate with float
        DllStructSetData($floatstruct, 1, $float)           ;write into float struct
        $pixel = DllStructGetData($dwordstruct, 1)          ;get dword from float and
        _GDIPlus_BitmapSetPixel($Image, $x, $y + 1, $pixel)

        $pixel = _GDIPlus_BitmapGetPixel($Image, $x + 1, $y + 1)
        DllStructSetData($dwordstruct, 1, $pixel)           ;set into DWORD struct
        $col = DllStructGetData($floatstruct, 1)            ;read float from DWORD
        $float = $col + (1 / 16 * $quant_error)             ;calculate with float
        DllStructSetData($floatstruct, 1, $float)           ;write into float struct
        $pixel = DllStructGetData($dwordstruct, 1)          ;get dword from float and
        _GDIPlus_BitmapSetPixel($Image, $x + 1, $y + 1, $pixel)

    Next
    _GDIPlus_GraphicsDrawImageRect($Graphics, $Image, 0, 0, $Width, $Height)           ; to see the errors quick instead of waiting for the whole image

Next

_GDIPlus_GraphicsDrawImageRect($Graphics, $Image, 0, 0, $Width, $Height)


While GUIGetMsg <> -3
    Sleep(10)
WEnd

Func _exit()
    Exit
EndFunc                                                     ;==>_exit

Interesting side effect:

If an image is filled with 50% gray the result of Floyd-Steinberg should be a checkboard pattern. In the "real world", floating point numbers have restrictions, mentioned hundrets of thousands of times here in this forum....

So at the end, in the "real world" (of floating point numbers) there is an error which shows some artefacts in the "checkboard" pattern ( 50% gray as test50.png).

These errors are inevitably and independant of the used computer language. 

 

Floyd Steinberg dithering.zip

Edited by AndyG
Link to comment
Share on other sites

Posted (edited)

Beautiful, exactly what I need, much appreciated, very close to the PaintShopPro results I've been using. :)

I kinda figured it had something to do with floating point, reading around many websites, but from there to actually fixing it... 😛

Now to figure out how to speed it up, I usually use c-dll's though rather simple ones using TCC, but maybe it'll work out,

Thanks again, much appreciated. :) 

Edited by Werty

Some guy's script + some other guy's script = my script!

Link to comment
Share on other sites

Posted (edited)

I put the image in a struct and looped the dither stuff, faster but no preview.

#include <GDIPlus.au3>
HotKeySet("{ESC}", "_exit")

_GDIPlus_Startup()

Global $Floyd[12] = [7, 3, 5, 1, 1, -1, 0, 1, 0, 1, 1, 1]
Global $Image = _GDIPlus_BitmapCreateFromFile("graytest.png")
Global $Width = _GDIPlus_ImageGetWidth($Image), $Height = _GDIPlus_ImageGetHeight($Image)

$Gui = GUICreate("Floyd-Steinberg Dithering", $Width, $Height)
       GUISetState()

   $Graphics = _GDIPlus_GraphicsCreateFromHWND($Gui)
               _GDIPlus_GraphicsDrawImageRect($Graphics, $Image, 0, 0, $Width, $Height)
    $Bitmap1 = _GDIPlus_BitmapLockBits($Image, 0, 0, $Width, $Height, BitOR($GDIP_ILMWRITE, $GDIP_ILMREAD), $GDIP_PXF32ARGB)
    $Pixels1 =  DllStructCreate("dword[" & $Width * $Height & "];", DllStructGetData($Bitmap1, "Scan0"))
$floatstruct =  DllStructCreate("float")
$dwordstruct =  DllStructCreate("dword", DllStructGetPtr($floatstruct))

;AARRGGBB to float
For $y = 0 To $Height - 1
  $RowOffset = $y * $Width + 1
    For $x = 0 To $Width - 1
    $oldPixel = Dec(Hex(DllStructGetData($Pixels1, 1, $x + $RowOffset), 2)) / 255
                DllStructSetData($floatstruct, 1, $oldPixel)                   ;write into float struct
    $oldPixel = DllStructGetData($dwordstruct, 1)       ;read as dword (aka "pixel" AARRGGBB)
                DllStructSetData($Pixels1, 1, $oldPixel, $x + $RowOffset)      ;store the float number as "pixel" AARRGGBB
    Next
Next

For $y = 0 To $Height - 1
      $RowOffset = $y * $Width + 1
    For $x = 0 To $Width - 1
        $oldpixel = DllStructGetData($Pixels1, 1, $x + $RowOffset)
                    DllStructSetData($dwordstruct, 1, $oldPixel)        ;write into dwordstruct (place of floatstruct)
        $oldPixel = DllStructGetData($floatstruct, 1)                   ;read float
        $newpixel = ($oldpixel <= 0.5) ? 0:1 
                    DllStructSetData($Pixels1, 1, "0xFF" & Hex($newpixel * 255, 2) & Hex($newpixel * 255, 2) & Hex($newpixel * 255, 2), $x + $RowOffset)
     $quant_error = $oldPixel - $newpixel

        ;-------Floyd-Steinberg
    For $Loop  = 0 To 3
        $pixel = DllStructGetData($Pixels1, 1, ($x + $Floyd[$Loop+4]) + ($y + $Floyd[$Loop+8]) * $Width + 1);get pixel integer/DWORD AARRGGBB
                 DllStructSetData($dwordstruct, 1, $pixel)                                                  ;set into DWORD struct
          $col = DllStructGetData($floatstruct, 1)                                                          ;read float from DWORD
        $float = $col + ($Floyd[$Loop] / 16 * $quant_error)                                                 ;calculate with float
                 DllStructSetData($floatstruct, 1, $float)                                                  ;write into float struct
        $pixel = DllStructGetData($dwordstruct, 1)                                                          ;get dword from float and
                 DllStructSetData($Pixels1, 1, $pixel, ($x + $Floyd[$Loop+4]) + ($y + $Floyd[$Loop+8]) * $Width + 1) ;write the "float" as a "pixel"
    Next
    Next
Next
_GDIPlus_BitmapUnlockBits($Image, $Bitmap1)
_GDIPlus_GraphicsDrawImageRect($Graphics, $Image, 0, 0, $Width, $Height)

While GUIGetMsg <> -3
    Sleep(10)
WEnd

Func _exit()
    Exit
EndFunc

 

Edited by Werty

Some guy's script + some other guy's script = my script!

Link to comment
Share on other sites

@Werty I updated 

Just check out the example _GDIPlus_BitmapApplyFilter_Indexed.au3.

This time I added also a x64 DLL but not fully tested.

 

The result should look like this here with Floyd-Steinberg dithering:

image-2024-04-16-134616967.png

Edited by UEZ

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

Hey UEZ,

nice one as always ^^

I found a very cool python script here https://scipython.com/blog/floyd-steinberg-dithering/

After some tests I have realised that I can get similar results with your script when multiplying the $iColors with 3 (approximately^^).

But with low numbers of $iColors, something strange is happening:

 Global $iColors = 16, $iDitherMode =1

image.png.4a9fffc0dbaf7d192b1b4886ce0985e0.png


Changing to $iColors = 17, $iDitherMode =1 , the result looks much better. What is the reason for that behaviour?

image.png.32cc8c87e78a864ac0cd488347f7c2d9.png

Link to comment
Share on other sites

13 hours ago, UEZ said:

 I updated 

Thanks, awesome work as usual, unfortunately I know nothing about freebasic and kinda need it to be "my own" code, with help and inspiration from others ofcourse, this is just a small part of a bigger project, but I will certainly be using it till I get around to attempting making a c-dll, that will include other stuff also, I've just been busy with other parts after getting this atleast working so I could get on with other stuff.

But damn it's fast. :D

 

Some guy's script + some other guy's script = my script!

Link to comment
Share on other sites

Posted (edited)

How about going the other way, more than 256 grays...

Guess how many grays this image has. :)

Spoiler

1401 grays :)

earringgrays.png

Or make your own...

#Include <GDIPlus.au3>
HotKeySet("{ESC}", "_exit")

_GDIPlus_Startup()

  $Image = _GDIPlus_BitmapCreateFromFile("image.png")
 $Width  = _GDIPlus_ImageGetWidth($Image)
 $Height = _GDIPlus_ImageGetHeight($Image)

    $GUI = GUICreate("AmazingGrays", $Width, $Height);, -1, -1)
           GUISetState()

$Graphics = _GDIPlus_GraphicsCreateFromHWND($Gui)
            _GDIPlus_GraphicsDrawImageRect ($Graphics,$Image, 0, 0, $Width, $Height)
  $Bitmap = _GDIPlus_BitmapLockBits($Image, 0, 0, $Width, $Height, BitOR($GDIP_ILMWRITE, $GDIP_ILMREAD), $GDIP_PXF32ARGB)
  $Pixels =  DllStructCreate("dword[" & $Width * $Height & "];", DllStructGetData($Bitmap, "Scan0"))

For $Loop = 1 To $Width * $Height
   $Pixel = DllStructGetData($Pixels, 1, $Loop)
     $Red = Dec(StringMid(Hex($Pixel, 6), 1, 2))
   $Green = Dec(StringMid(Hex($Pixel, 6), 3, 2))
    $Blue = Dec(StringMid(Hex($Pixel, 6), 5, 2))
 Local $R = 0, $G = 0, $B = 0
    $LUMA = (($Red*.3) + ($Green*.59) + ($Blue*.11)/3)
  $AGrays =  $LUMA - Int($LUMA)
    $LUMA = Int($LUMA)

Switch StringFormat("%.2f", $AGrays)
   Case .05 To .18
      $B = 1
   Case .19 To .34
      $G = 1
   Case .35 To .50
      $B = 1
      $G = 1
   Case .51 To .66
      $R = 1
   Case .67 To .82
      $R = 1
      $B = 1
   Case .83 To .95
      $R = 1
      $G = 1
EndSwitch
        DllStructSetData($Pixels, 1, "0xFF" & Hex($LUMA + $R, 2) & Hex($LUMA + $G, 2) & Hex($LUMA + $B, 2), $Loop)
Next

_GDIPlus_BitmapUnlockBits($Image, $Bitmap)
_GDIPlus_GraphicsDrawImageRect($Graphics, $Image, 0, 0, $Width, $Height)

Do
Until GUIGetMsg() = - 3

Func _exit()
    Exit
 EndFunc

It's using "near gray" colors, like (155,155,154) looks gray eventhough it's not pure, also called "pseudo gray" or "fake gray", good for photographers that wanna print high resolution images as grayscale on big posters.

/edit

Comparison between standard averaging on the left that came to 246 grays and AmazingGrays on the right came to 1512 grays.

 

 

averageVSagray.png

Edited by Werty

Some guy's script + some other guy's script = my script!

Link to comment
Share on other sites

The color reducing code is based on the GDIPlus function GdipGetImagePalette which is unfortunately not the best algorithm to choose the best colors. Additionally it doesn't utilize the full color range. For example if you create a 8 bit bitmap it doesn't use the full color scope of 256 colors.

The color formats which I use is 2-bit, 4-bit and 8 bit -> 2, 16 and 256 colors. Nothing in between will be calculated.

@AndyG that is the reason why it looks better when $iColors is > 16. It will set it to 256 color bitmap (8 bit).

@Werty the DLL is using heavily GDIPlus functions and the For/Next loops are much faster in Freebasic than in Autoit. In the DLL I added also a function call "_GDIPlus_BitmapCreateGreyscale" which converts the image to 256 greyscale colors using (iR * 213 + iG * 715 + iB * 72) / 1000 calculation. Let me add your "fake"grey  to the DLL...

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

7 minutes ago, UEZ said:

Let me add your "fake"grey  to the DLL...

If it was a question then sure, please go ahead. :)

Some guy's script + some other guy's script = my script!

Link to comment
Share on other sites

12 hours ago, Werty said:

If it was a question then sure, please go ahead. :)

Done. Check out 

Download the 7-Zip archive and look for _GDIPlus_BitmapApplyFilter_FakeGreyscale.au3 example.

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

Posted (edited)

Nice, how does the code look in freebasic, not that I'm gonna start with freebasic, just curious.

I noticed we dont get same results, I take it you didnt use LUMA but the " (iR * 213 + iG * 715 + iB * 72) / 1000" you mentioned, quite a difference, i get...

Eagle.jpg: yours 1020 grays, mine 1654

Face, png: yours 978, mine 1576

Maybe a switch parameter where the user can select between "Average", "LUMA", "Yours" and whatever other there might be.

Edited by Werty

Some guy's script + some other guy's script = my script!

Link to comment
Share on other sites

Here the function in FB:

Function _GDIPlus_BitmapCreateFakeGreyscale(hImage As Any Ptr, bGDI As BOOL = False) As Any Ptr Export
    Dim As Single iW, iH
    Dim As Double fGreys, fLuma
    Dim As Any Ptr hBitmap_Greyscale, hGDIBitmap
    Dim As BitmapData tBitmapData, tBitmapData_Greyscale
    Dim As Long iX, iY, iRowOffset, iColor, c, iR, iG, iB
    
    GdipGetImageDimension(hImage, @iW, @iH)
    
    Dim As RECT tRect = Type(0, 0, iW - 1, iH - 1)
    
    GdipCreateBitmapFromScan0(iW, iH, 0, PixelFormat32bppARGB, 0, @hBitmap_Greyscale)
    GdipBitmapLockBits(hBitmap_Greyscale, Cast(Any Ptr, @tRect), ImageLockModeWrite, PixelFormat32bppARGB, @tBitmapData_Greyscale)
    GdipBitmapLockBits(hImage, Cast(Any Ptr, @tRect), ImageLockModeRead, PixelFormat32bppARGB, @tBitmapData)
    
    For iY = 0 To iH - 1
        iRowOffset = iY * iW
        For iX = 0 To iW - 1
            iColor = Cast(ULong Ptr, tBitmapData.Scan0)[iRowOffset + iX]
            iR = (iColor Shr 16) And &hFF
            iG = (iColor Shr 8) And &hFF
            iB = iColor And &hFF
            fLuma = (iR * 213 + iG * 715 + iB * 72) / 1000
            'fLuma = ((iR * 0.3) + (iG * 0.59) + (iB * 0.11) / 3)
            fGreys = fLuma - CUByte(fLuma)
            fLuma = CUByte(fLuma)
            iR = 0
            iG = 0
            iB = 0
            Select Case fGreys
                Case 0.05 To 0.18
                    iB = 1
                Case 0.19 To 0.34
                    iG = 1
                Case 0.35 To 0.50
                    iB = 1
                    iG = 1
                Case 0.51 To 0.66
                    iR = 1
                Case 0.67 To 0.82
                    iR = 1
                    iB = 1
                Case 0.83 To 0.95
                    iR = 1
                    iG = 1
            End Select
            Cast(ULong Ptr, tBitmapData_Greyscale.Scan0)[iRowOffset + iX] = &hFF000000 Or ((fLuma + iR) Shl 16) Or ((fLuma + iG) Shl 8) Or (fLuma + iB) Shl 0
        Next
    Next
    
    GdipBitmapUnlockBits(hBitmap_Greyscale, @tBitmapData_Greyscale)
    GdipBitmapUnlockBits(hImage, @tBitmapData)
    If bGDI Then
        GdipCreateHBITMAPFromBitmap(hBitmap_Greyscale, @hGDIBitmap, &hFF000000)
        GdipDisposeImage(hBitmap_Greyscale)
        Return hGDIBitmap
    EndIf
    Return hBitmap_Greyscale
End Function

I will play around with different values...

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

Posted (edited)

Earlier i adjusted this part...

Select Case fGreys
                Case 0.05 To 0.18
                    iB = 1
                Case 0.19 To 0.34
                    iG = 1
                Case 0.35 To 0.50
                    iB = 1
                    iG = 1
                Case 0.51 To 0.66
                    iR = 1
                Case 0.67 To 0.82
                    iR = 1
                    iB = 1
                Case 0.83 To 0.95
                    iR = 1
                    iG = 1
            End Select

..to use only 2 decimal places, if the decimal places are longer than 2 it misses some going from case to case, scroll up and see the changes.

Edited by Werty

Some guy's script + some other guy's script = my script!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...