Jump to content

# Trouble with Floyd-Steinberg dithering

Go to solution Solved by AndyG,

## Recommended Posts

Posted (edited)

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)

Edited by Werty

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

##### Share on other sites

Posted (edited)

<delete>

I didn't modify anything, I just ran it

and this with Sierra2

<delete>

Edited by ioa747

I know that I know nothing

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

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

##### Share on other sites

Posted (edited)

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

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

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

Edited by AndyG
##### 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!

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

##### Share on other sites

Posted (edited)

@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:

Edited by UEZ

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

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

##### 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

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

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

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

##### 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

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.

Edited by Werty

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

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

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

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

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

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

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

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

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

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

##### Share on other sites

I changed it to 2 decimals, too. Now more colors will be used.

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

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

##### Share on other sites

That's more like it.  now I get...

Eagle 1785

Face 1690

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

## 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
• ### Recently Browsing   0 members

• No registered users viewing this page.
×

• Wiki

• Back

• #### Beta

• Git
• FAQ
×
• Create New...