Jump to content

Converting image to a defined pallet of colors


Recommended Posts

What I am trying to accomplish is to simplify a .PNG image and convert it to use only a specified pallet of colors. Eg if I have a pallet of 0xFF0000, 0x00FF00, 0x0000FF, it would change every pixel in the image to one of those three colors by whichever color is closest.

 

The simplest method I have tried is simply converting the image to a 16 bit bitmap, and that does simplify the pallet of the image, but I want to be able to specify what colors it specifies to. The other problem is the resulting saved .BMP file is much larger (file size) than the original, and I don't know if it's possible to make a 16 bit jpg.

 

It looks like _GDIPlus_BitmapConvertFormat is the solution but the help file doesn't go into how to use the pallet and I couldn't find an example that did what I needed. Can anyone help me?

0x616e2069646561206973206c696b652061206d616e20776974686f7574206120626f64792c20746f206669676874206f6e6520697320746f206e657665722077696e2e2e2e2e

Link to comment
Share on other sites

Something like this here?

 

GDI+ v1.1 required -> Windows 7+

#include <GDIPlus.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>


Global $sFile = FileOpenDialog("Selecet an image", "", "Images (*.jpg;*.png;*.bmp)")
If @error Then Exit MsgBox(16, "Error", "You must select an image!", 30)

_GDIPlus_Startup()

$hImage = _GDIPlus_ImageLoadFromFile($sFile)
$iW = _GDIPlus_ImageGetWidth($hImage)
$iH = _GDIPlus_ImageGetHeight($hImage)
$hGUI = GUICreate("GDI+ v1.1 needed", $iW * 2 + 30, $iH + 20)
GUISetState()

$hDC = _WinAPI_GetDC($hGUI)

$hGraphics1 = _GDIPlus_GraphicsCreateFromHWND($hGUI)
_GDIPlus_GraphicsDrawImageRect($hGraphics1, $hImage, 10, 10, $iW, $iH)
_GDIPlus_GraphicsDispose($hGraphics1)
_WinAPI_ReleaseDC($hGUI, $hDC)

$iColors = 4

Local $tPalette = _GDIPlus_PaletteInitialize(256, $GDIP_PaletteTypeFixedHalftone256, $iColors, False, $hImage)
ConsoleWrite(@error & @CRLF)
$tPalette.ARGB((1)) = 0xFF0000FF
$tPalette.ARGB((2)) = 0xFF00FF00
$tPalette.ARGB((3)) = 0xFFFF0000
$tPalette.ARGB((4)) = 0xFF000000

_GDIPlus_BitmapConvertFormat($hImage, $GDIP_PXF04INDEXED, $GDIP_DitherTypeDualSpiral8x8, $GDIP_PaletteTypeFixedHalftone256, $tPalette)
DllCall($__g_hGDIPDll, "int", "GdipSetImagePalette", "handle", $hImage, "struct*", $tPalette)

$hDC = _WinAPI_GetDC($hGUI)
$hGraphics2 = _GDIPlus_GraphicsCreateFromHDC($hDC)

_GDIPlus_GraphicsDrawImageRect($hGraphics2, $hImage, $iW + 20, 10, $iW, $iH)

Do
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            _WinAPI_ReleaseDC($hGUI, $hDC)
            _GDIPlus_GraphicsDispose($hGraphics2)
            _GDIPlus_ImageDispose($hImage)
            _GDIPlus_Shutdown()
            GUIDelete()
            Exit
    EndSwitch
Until False

 

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

I knew it would be UEZ to respond. You really are our local GDI expert.

That is really cool and i think we're on the right track, but I'm not looking to use various dots to make up the color, I want to make each area the solid color it's closest to in the pallet. For instance a4Dnd88.png turns into  CoGRTDb.png but the result I'm looking for is  COOWRLe.png

 

0x616e2069646561206973206c696b652061206d616e20776974686f7574206120626f64792c20746f206669676874206f6e6520697320746f206e657665722077696e2e2e2e2e

Link to comment
Share on other sites

Try this.

#include <Color.au3>
#include <GuiConstantsEx.au3>
#include <GDIPlus.au3>
#include <Misc.au3>
#include <ScreenCapture.au3>

Opt('MustDeclareVars', 1)

Global $width, $height, $hGUI1, $hGUI2, $hImage, $hImage2, $hGraphic1, $hGraphic2, $iX, $iY, $sImageIn

_Main()

Func _Main()
    ; Capture top left corner of the screen
    ;$sImageIn = "GDIPlus_Image.png"
    ;_ScreenCapture_Capture($sImageIn, 0, 0, 400, 400)
    ;Or

    ;$sImageIn = FileOpenDialog("First image", "", "All images (*.jpg;*.png;*.gif;*.bmp;)")
    ;If $sImageIn = "" Then Exit
    ;ConsoleWrite($sImageIn & @LF)
    ;Or

    InetGet("https://i.imgur.com/a4Dnd88.png", @ScriptDir & "\a4Dnd88.png")
    $sImageIn = @ScriptDir & "\a4Dnd88.png"
    ;or

    ;InetGet("http://img11.nnm.ru/a/1/5/b/0/1dba2209d5a133d9e84431a5587.jpg","FireWorksSydney.jpg")
    ;$sImageIn = "FireWorksSydney.jpg"

    ; Create a GUI for the original image
    $hGUI1 = GUICreate("Original", 400, 400, 0, 0)
    GUISetState()

    ; Create a GUI for the zoomed image
    $hGUI2 = GUICreate("Ctrl + Left click to save image", 400, 400, 0, 400)
    GUISetState()
    GUIRegisterMsg(0xF, "MY_PAINT")

    ; Initialize GDI+ library and load image
    _GDIPlus_Startup()
    $hImage = _GDIPlus_ImageLoadFromFile($sImageIn)
    $iX = _GDIPlus_ImageGetWidth($hImage)
    $iY = _GDIPlus_ImageGetHeight($hImage)
    ConsoleWrite($iX & " x " & $iY & @CRLF)

    ; Draw original image
    $hGraphic1 = _GDIPlus_GraphicsCreateFromHWND($hGUI1)
    _GDIPlus_GraphicsDrawImageRectRect($hGraphic1, $hImage, 0, 0, $iX, $iY, 0, 0, 400, 400)

    $hImage2 = _ImageColorFunc($hImage, "CF") ; <======= Use function CF() on each pixel.

    ; Draw returned image from _ImageColorFunc
    $hGraphic2 = _GDIPlus_GraphicsCreateFromHWND($hGUI2)
    _GDIPlus_GraphicsDrawImageRectRect($hGraphic2, $hImage2, 0, 0, $iX, $iY, 0, 0, 400, 400)

    ; Loop until user exits
    Do
        If _IsPressed("01") And _IsPressed("11") Then ; Left click + Ctrl key to save image.
            Do
            Until Not _IsPressed("01")
            Local $PathFile = FileSaveDialog("Choose a name.", @ScriptDir & "\", "Images (*.bmp;*.jpg;*.png;*.gif)|All files (*.*)", 16, "ColourModify.png")
            If Not @error Then
                _GDIPlus_ImageSaveToFile($hImage2, $PathFile)
                ;ShellExecute($PathFile)
            EndIf
        EndIf
        Sleep(10)
    Until GUIGetMsg() = $GUI_EVENT_CLOSE

    ; Release resources
    _GDIPlus_GraphicsDispose($hGraphic1)
    _GDIPlus_GraphicsDispose($hGraphic2)
    _GDIPlus_ImageDispose($hImage)
    _GDIPlus_ImageDispose($hImage2)
    _GDIPlus_Shutdown()
EndFunc   ;==>_Main

; ========= _ImageColorFunc($hImage, $ColorFunc) =====================
;Paramerers:-
; $hImage   - Handle to a Bitmap object
; $ColorFunc - A function that will be applied to every pixel in image.
;
Func _ImageColorFunc($hImage, $ColorFunc)
    Local $Reslt, $stride, $format, $Scan0, $iIW, $iIH, $hBitmap1
    Local $v_BufferA, $AllPixels, $sREResult1, $sResult

    $iIW = _GDIPlus_ImageGetWidth($hImage)
    $iIH = _GDIPlus_ImageGetHeight($hImage)
    ;ConsoleWrite($iIW & "  " & $iIH & @LF)
    $hBitmap1 = _GDIPlus_BitmapCloneArea($hImage, 0, 0, $iIW, $iIH, $GDIP_PXF32ARGB)

    ; Locks a portion of a bitmap for reading or writing
    $Reslt = _GDIPlus_BitmapLockBits($hBitmap1, 0, 0, $iIW, $iIH, BitOR($GDIP_ILMREAD, $GDIP_ILMWRITE), $GDIP_PXF32ARGB)

    ;Get the returned values of _GDIPlus_BitmapLockBits ()
    $width = DllStructGetData($Reslt, "width")
    $height = DllStructGetData($Reslt, "height")
    $stride = DllStructGetData($Reslt, "stride")
    $format = DllStructGetData($Reslt, "format")
    $Scan0 = DllStructGetData($Reslt, "Scan0")

    $v_BufferA = DllStructCreate("byte[" & $height * $width * 4 & "]", $Scan0) ; Create DLL structure for all pixels
    $AllPixels = DllStructGetData($v_BufferA, 1)
    ;ConsoleWrite("$AllPixels, raw data, first few colours = " & StringRegExpReplace($AllPixels, "(.{98})(.*)", "\1") & @CRLF)

    ; Searches on this string - $sREResult1 whch has the prefix "0x"  removed and a space put between pixels 8 characters long.
    $sREResult1 = StringRegExpReplace(StringTrimLeft($AllPixels, 2), "(.{8})", "\1 ")
    ;ConsoleWrite("$sREResult1 first few colours = " & StringRegExpReplace($sREResult1, "(.{81})(.*)", "\1") & @CRLF)

    ; Create an executable string with each pixel as a parameter of $ColorFunc function and remove all spaces.
    $sResult = StringStripWS(StringRegExpReplace($sREResult1, "(.{8}) ", $ColorFunc & '("\1")&'), 8)
    ;ConsoleWrite("$sREResult1 first few colours = " & StringRegExpReplace($sResult, "(.{81})(.*)", "\1") & @CRLF)

    ; Remove trailing "&", execute string, $sResult, and, add a leading, "0x.
    $sResult = "0x" & Execute(StringTrimRight($sResult, 1))
    ;ConsoleWrite("$sREResult1 first 9 colours = " & StringRegExpReplace($sResult, "(.{81})(.*)", "\1") & @CRLF)

    DllStructSetData($v_BufferA, 1, $sResult)
    _GDIPlus_BitmapUnlockBits($hBitmap1, $Reslt) ; Releases the locked region

    Return $hBitmap1
EndFunc   ;==>_ImageColorFunc

Func MY_PAINT($hWnd, $msg, $wParam, $lParam)
    If $hWnd = $hGUI1 Then _GDIPlus_GraphicsDrawImageRectRect($hGraphic1, $hImage, 0, 0, $iX, $iY, 0, 0, 400, 400) ; _GDIPlus_GraphicsDrawImageRect($hGraphic1, $hImage, 0, 0, $width, $height)
    If $hWnd = $hGUI2 Then _GDIPlus_GraphicsDrawImageRectRect($hGraphic2, $hImage2, 0, 0, $iX, $iY, 0, 0, 400, 400) ; _GDIPlus_GraphicsDrawImageRect($hGraphic2, $hImage2, 0, 0, $width, $height)
    Return $GUI_RUNDEFMSG
EndFunc   ;==>MY_PAINT

; The function used as a parameter in _ImageColorFunc() function.
Func CF($iColor)
    Local $aPalet = [0x0000FF, 0x00FF00, 0xFF0000, 0x000000], $iCol
    $iColor = "0x" & StringRegExpReplace($iColor, "(.{2})(.{2})(.{2})(.{2})", "$3$2$1") ; convert to AARRGGBB
    If _ColorGetRed($iColor) >= _ColorGetGreen($iColor) And _ColorGetRed($iColor) >= _ColorGetBlue($iColor) Then
        $iCol = $aPalet[0]
        If _ColorGetRed($iColor) <= 0x80 Then $iCol = $aPalet[3]
    EndIf
    If _ColorGetGreen($iColor) >= _ColorGetRed($iColor) And _ColorGetGreen($iColor) >= _ColorGetBlue($iColor) Then
        $iCol = $aPalet[1]
        If _ColorGetGreen($iColor) <= 0x80 Then $iCol = $aPalet[3]
    EndIf
    If _ColorGetBlue($iColor) >= _ColorGetRed($iColor) And _ColorGetBlue($iColor) >= _ColorGetGreen($iColor) Then
        $iCol = $aPalet[2]
        If _ColorGetBlue($iColor) <= 0x80 Then $iCol = $aPalet[3]
    EndIf
    Return Hex($iCol, 6) & "FF"
EndFunc   ;==>CF

 

Link to comment
Share on other sites

@corgano you have to be more precise what you want.

If you disable dithering in the code from post #2 you will get very similar results:

#include <GDIPlus.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>


Global $sFile = FileOpenDialog("Selecet an image", "", "Images (*.jpg;*.png;*.bmp)")
If @error Then Exit MsgBox(16, "Error", "You must select an image!", 30)

_GDIPlus_Startup()

Global $hImage = _GDIPlus_ImageLoadFromFile($sFile)
Global $iW = _GDIPlus_ImageGetWidth($hImage)
Global $iH = _GDIPlus_ImageGetHeight($hImage)
Global $hGUI = GUICreate("GDI+ v1.1 needed", $iW * 2 + 30, $iH + 20)
GUISetState()


Global $hGraphics1 = _GDIPlus_GraphicsCreateFromHWND($hGUI)
_GDIPlus_GraphicsDrawImageRect($hGraphics1, $hImage, 10, 10, $iW, $iH)
_GDIPlus_GraphicsDispose($hGraphics1)

Global $iColors = 4

Global $tPalette = _GDIPlus_PaletteInitialize(256, $GDIP_PaletteTypeOptimal, $iColors, False, $hImage)
$tPalette.ARGB((1)) = 0xFF0000FF
$tPalette.ARGB((2)) = 0xFF00FF00
$tPalette.ARGB((3)) = 0xFFFF0000
$tPalette.ARGB((4)) = 0xFF000000

_GDIPlus_BitmapConvertFormat($hImage, $GDIP_PXF04INDEXED, $GDIP_DitherTypeSolid, $GDIP_PaletteTypeOptimal, $tPalette)
DllCall($__g_hGDIPDll, "int", "GdipSetImagePalette", "handle", $hImage, "struct*", $tPalette)

Global $hGraphics2 = _GDIPlus_GraphicsCreateFromHWND($hGUI)

_GDIPlus_GraphicsDrawImageRect($hGraphics2, $hImage, $iW + 20, 10, $iW, $iH)

Do
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            _GDIPlus_GraphicsDispose($hGraphics1)
            _GDIPlus_GraphicsDispose($hGraphics2)
            _GDIPlus_ImageDispose($hImage)
            _GDIPlus_Shutdown()
            GUIDelete()
            Exit
    EndSwitch
Until False

 

@Malkey nice to see you posting GDI+ stuff again. I learned so much from your posts! :)

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

How about  _GDIPlus_ImageAttributesSetRemapTable ?

Global $aRemapTable[5][2]
$aRemapTable[0][0] = 4
$aRemapTable[1][0] = 0xFFD3B4BA
$aRemapTable[1][1] = 0xFFFF0000
$aRemapTable[2][0] = 0xFFB5D7B0
$aRemapTable[2][1] = 0xFF00FF00
$aRemapTable[3][0] = 0xFFACAFDB
$aRemapTable[3][1] = 0xFF0000FF
$aRemapTable[4][0] = 0xFF646464
$aRemapTable[4][1] = 0xFF000000

 

Or simply fill the area

#include <GDIPlus.au3>

_GDIPlus_Startup ()

$hImage = _GDIPlus_ImageLoadFromFile("a4Dnd88.png")
$hBmp = _GDIPlus_BitmapCreateHBITMAPFromBitmap($hImage)
_GDIPlus_ImageDispose($hImage)

$hCDC = _WinAPI_CreateCompatibleDC(0)
$hOldBmp = _WinAPI_SelectObject($hCDC, $hBmp)

$hBrush = _WinAPI_GetStockObject($DC_BRUSH)
$hOldBrush = _WinAPI_SelectObject($hCDC, $hBrush)

_WinAPI_SetDCBrushColor($hCDC, 0xFF0000)
_WinAPI_ExtFloodFill($hCDC, 0, 0, _WinAPI_GetPixel($hCDC, 0, 0), 1)
_WinAPI_SetDCBrushColor($hCDC, 0x00FF00)
_WinAPI_ExtFloodFill($hCDC, 100, 0, _WinAPI_GetPixel($hCDC, 100, 0), 1)
_WinAPI_SetDCBrushColor($hCDC, 0x0000FF)
_WinAPI_ExtFloodFill($hCDC, 0, 100, _WinAPI_GetPixel($hCDC, 0, 100), 1)
_WinAPI_SetDCBrushColor($hCDC, 0x000000)
_WinAPI_ExtFloodFill($hCDC, 100, 100, _WinAPI_GetPixel($hCDC, 100, 100), 1)

$hImage = _GDIPlus_BitmapCreateFromHBITMAP($hBmp)
_GDIPlus_ImageSaveToFile($hImage, "result.png")
_GDIPlus_ImageDispose($hImage)

_WinAPI_SelectObject($hCDC, $hOldBrush)
_WinAPI_DeleteObject($hBrush)
_WinAPI_SelectObject($hCDC, $hOldBmp)
_WinAPI_DeleteObject($hBmp)
_WinAPI_DeleteDC($hCDC)

_GDIPlus_ShutDown()

 

Link to comment
Share on other sites

@InnI the magic key word is here color quantization. Probably corgano's example is just a simple one for demonstration purposes.

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

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

×
×
  • Create New...