Hash Wert von Bild erstellen

  • Hallo Zusammen,

    ich versuche gerade von einem Screenshoot einen Bereich auszuschneiden und von diesem einen Hashwert mittels MD5 zu generieren. Um diesen dann ganz im Sinne einer Pixelchecksum mit einem anderen Hashwert (welcher auf dem selben Wege entstanden ist) zu vergleichen. In (meiner) Theorie sollte dies die gleiche Eindeutigkeit bei einem Bildervergleich wie die Pixelchecksum-Methode ergeben.
    Das ist ein reines neugier Projekt und zielt auf die eigentliche Machbarkeit und weniger auf einen Anwendungsnutzen ab. Mein Code ist soweit noch sehr unfertig da ich gerade erst seit einigen Minuten dran sitze und mich Frage ob es generell machbar ist. Nichts desto trotz hier ein kleines Beispiel (momentan nur ein Screenshoot vom Desktop und die Ermittlung des Hashwertes):

    [autoit][/autoit][autoit][/autoit][autoit][/autoit][autoit]

    _GDIPlus_Startup()
    Send("{PRINTSCREEN}")
    _ClipBoard_Open(0)
    $BILDinZWSPEICHER = _ClipBoard_GetDataEx($CF_BITMAP)
    $bild = _GDIPlus_BitmapCreateFromHBITMAP($BILDinZWSPEICHER)

    [/autoit][autoit][/autoit][autoit][/autoit][autoit][/autoit][autoit]

    _Crypt_Startup()
    $sFile = $bild;"test.png"
    $MD5 = _Crypt_HashFile($sFile, $CALG_MD5)
    MsgBox(0, 'Hash', "MD5 von '"&$sFile&"': " &@CRLF & Hex($MD5))
    _Crypt_Shutdown()
    _GDIPlus_Shutdown()

    [/autoit][autoit][/autoit][autoit][/autoit][autoit][/autoit]

    Also der Hashwert von gespeicherten Bilder funktioniert aber ich möchte die Bilder nicht immer speichern müssen.

  • Hi,
    alpines,
    bevor man irgendetwas "konvertiert" sollte es erst einmal vorhanden sein...

    smartpart82,
    du erhälst mit deinen Funktionen nur sog. Handles auf das Bild.
    Um an die (Pixel)-Daten zu kommen, erstellst du am einfachsten eine Byte-Struct mit der Größe und an der Position(Adresse bzw. Pointer) der Bitmap und liest ab dieser Position die "Pixel" aus...

    //EDIT es gibt sicherlich Lösungen mit GDI+, die nichts anderes machen, als die WIN_API zu wrappern^^

    Spoiler anzeigen
    [autoit]

    #include <Crypt.au3>
    #include <WinAPI.au3>

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    $x = 0 ;Bild Position , Höhe und Breite
    $y = 0
    $w = @DesktopWidth
    $h = @DesktopHeight

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Local $hDC = _WinAPI_GetDC(0) ;HDC Desktop

    [/autoit] [autoit][/autoit] [autoit]

    Global $ptr, $hbmp ;Pointer und ggf handle, falls man irgendwann einmal GDI(+) braucht^^
    $DC_bitmap = _CreateNewBmp32($w, $h, $ptr, $hbmp) ;Bitmap erstellen
    $struct = DllStructCreate("byte[" & $w * $h * 4 & "]", $ptr) ;Struct an der Position der Bitmap erstellen

    [/autoit] [autoit][/autoit] [autoit]

    _WinAPI_BitBlt($DC_bitmap, $x, $y, $w, $h, $hDC, 0, 0, 0xCC0020);Bitmap in DC blitten

    [/autoit] [autoit][/autoit] [autoit]

    $pixeldata = DllStructGetData($struct, 1) ;Das sind die "Pixel" RGBA, Achtung, hier steht noch ein 0x davor !!

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    _Crypt_Startup()
    $MD5 = _Crypt_HashData($pixeldata, $CALG_MD5)
    MsgBox(0, 'Hash', "MD5: " & @CRLF & Hex($MD5))
    _Crypt_Shutdown()

    [/autoit] [autoit][/autoit] [autoit]

    _DeleteBitmap32($DC_bitmap, $ptr, $hbmp) ; bitmap aus dem Speicher

    [/autoit] [autoit][/autoit] [autoit]

    exit

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _CreateNewBmp32($iwidth, $iheight, ByRef $ptr, ByRef $hbmp) ;erstellt leere 32-bit-Bitmap; Rückgabe DC und ptr und handle auf die Bitmapdaten
    ;by Andy
    $tagBITMAPINFO_dafuqbeta = "struct ;dword Size;long Width;long Height;word Planes;word BitCount;dword Compression;dword SizeImage;long XPelsPerMeter;long YPelsPerMeter;dword ClrUsed;dword ClrImportant;endstruct;dword RGBQuad"
    Local $hcdc = _WinAPI_CreateCompatibleDC(0) ;Desktop-Kompatiblen DeviceContext erstellen lassen
    Local $tBMI = DllStructCreate($tagBITMAPINFO_dafuqbeta) ;Struktur der Bitmapinfo erstellen und Daten eintragen
    DllStructSetData($tBMI, "Size", DllStructGetSize($tBMI) - 4) ;Structgröße abzüglich der Daten für die Palette
    DllStructSetData($tBMI, "Width", $iwidth)
    DllStructSetData($tBMI, "Height", -$iheight) ;minus =standard = bottomup
    DllStructSetData($tBMI, "Planes", 1)
    DllStructSetData($tBMI, "BitCount", 32) ;32 Bit = 4 Bytes => AABBGGRR
    Local $adib = DllCall('gdi32.dll', 'ptr', 'CreateDIBSection', 'hwnd', 0, 'ptr', DllStructGetPtr($tBMI), 'uint', 0, 'ptr*', 0, 'ptr', 0, 'uint', 0)
    $hbmp = $adib[0] ;hbitmap handle auf die Bitmap, auch per GDI+ zu verwenden
    $ptr = $adib[4] ;pointer auf den Anfang der Bitmapdaten, vom Assembler verwendet
    _WinAPI_SelectObject($hcdc, $hbmp) ;objekt hbitmap in DC
    Return $hcdc ;DC der Bitmap zurückgeben
    EndFunc ;==>_CreateNewBmp32

    [/autoit] [autoit][/autoit] [autoit]

    Func _DeleteBitmap32($DC, $ptr, $hbmp)
    _WinAPI_DeleteDC($DC)
    _WinAPI_DeleteObject($hbmp)
    $ptr = 0
    EndFunc ;==>_DeleteBitmap32

    [/autoit] [autoit][/autoit] [autoit][/autoit]

    Was das Ganze allerdings soll, ist mir klar^^
    BOTBOTBOTBOTBOT......
    Kein Mensch erstellt einen Hash um 2 Strings zu vergleichen!

    ciao
    Andy


    "Schlechtes Benehmen halten die Leute doch nur deswegen für eine Art Vorrecht, weil keiner ihnen aufs Maul haut." Klaus Kinski
    "Hint: Write comments after each line. So you can (better) see what your program does and what it not does. And we can see what you're thinking what your program does and we can point to the missunderstandings." A-Jay

    Wie man Fragen richtig stellt... Tutorial: Wie man Script-Fehler findet und beseitigt...X-Y-Problem

    4 Mal editiert, zuletzt von Andy (17. November 2013 um 20:29)

  • Hallo,

    danke euch beiden für den Input.

    Andy

    Falsch geraten kein Bot ;) ehr eine kuriose Entdeckung. Windows ist nicht mein OS und läuft nur in einer VM. In der programmiere ich auch. Nun habe ich vor einiger Zeit festgestellt, dass WPF in der VM "unsichtbar " ist. Man sieht sie und kann sie auch ganz normal nutzen nur Autoit erkennt sie nicht. Macht man einen Screencapture oder eine Pixelchecksum wird der Bereich hinter dem offenen Fenster genommen. Mir ist das aufgefallen als ich ein Programm heruntergeladen habe wobei es sich um eine in Autoit programmierte Bildschirmlupe handelte in der die WPF bei Mausover nicht sichtbar war sondern der Desktop dahinter. Auf einem klassischen Windows-Screenshoot hingegen ist die WPF dann wieder ganz normal zu erkennen.
    Nun lässt mich das einfach nicht in ruhe und ich würde das Problem gern irgendwie lösen, Ich dachte es ginge evtl über Windows-Screenshoot machen Bereich ausschneiden und dann Hashwert als eine Art Checksum.

    Das ist jetzt ein Bereich indem ich nicht so fitt bin aber wenn ich es richtig lese werden die Werte vom Desktop genommen. Dies wird leider nicht funktionieren. Geht das auch mit einem bild das im Zwischenspeicher ist? Bzw kann ich über das Handel vom Bild an die Daten kommen?

    Einmal editiert, zuletzt von smartpart82 (17. November 2013 um 21:21)

  • In $pixeldata stecken hinteineinandergereiht alle Pixeldaten drin. (das 0x abschneiden).
    Den String kannst du selbstverständlich auch hashen ohne ihn vorher auf die Platte zu schreiben.

    lg
    M

  • Hallo nochmal und einen ganz ganz großen Dank für die ganze Hilfe die man hier bekommt. Ich find euch klasse :thumbup:

    ich habe es gerade nochmal ausprobiert und das Programm geht leider nicht wie gewünscht. Bitte immer vor dem Hintergrund betrachten dass ich gerade noch neu auf dem Gebiet bin.

    Wenn ich es richtig verstehe nimmt es die Pixel direkt vom Desktop. Das ist aber des eigentliche Problem da er das Fenster nicht erkennt werden die Pixel die eigentlich vom Fenster verdeckt sind genommen anstelle die des WPF Fensters (also er nimmt die Pixel des hinterm dem WPF liegenden Desktops). Ich hab in Autoit bislang noch keine Methode gefunden die das Fenster erkennt. Pixelchecksum, Pixelgetcolor, _Screencpture funktionieren leider alle nicht bzw. nehmen immer die Werte vom Desktop die eigentlich von dem WPF Fenster verdeckt sind.

    Meine Lösung soweit:

    - Screenshoot vom Desktop mittels Printscreen-Taste machen (Drucken Taste)
    - mittels GDI Ausschnitt des Bildes in eine GUI zeichnen und dann bearbeiten

    also es geht ich finde es halt nur sehr umständlich.

    Bislang taucht das WPF Fenster nur auf dem Windows Screenshoot auf (Mittels Printscreen). Nun dachte ich wenn ich statt es in eine GUI zu zeichnen den Hashwert ermittle spare ich mir ein paar Schritte.
    Da sie aber nur auf dem Screenshoot auftaucht müsste ich so vorgehen (in meiner Theorie)

    - Printscreen drücken
    - Aus dem Screenshoot im Speicher einen Bereich ausschneiden
    - von dem Ausgeschnittenen Bereich den Hashwert nehmen
    - der Hashwert sollte dann immer gleich sein sofern der Bereich gleich ist

    ABER geht das so überhaupt? Ob das nun schneller oder langsamer ist ist mir eigentlich egal ich würde es halt nur gern lösen :?:.

  • Probiere es mal damit (benötigt die aktuelle Beta)!:

    [autoit]


    #AutoIt3Wrapper_Version=b
    #include <Clipboard.au3>
    #include <Crypt.au3>
    #include <Screencapture.au3>

    [/autoit][autoit][/autoit][autoit]

    _GDIPlus_Startup()
    $BILDinZWSPEICHER = _ScreenCapture_Capture("", 0, 0, 9, 9, 0) ; 10x10 an der Stelle 0,0
    $bild = _GDIPlus_BitmapCreateFromHBITMAP($BILDinZWSPEICHER)
    $sFile = _GDIPlus_StreamImage2BinaryString($bild, "PNG")
    ConsoleWrite($sFile & @LF)
    _WinAPI_DeleteObject($BILDinZWSPEICHER)
    _GDIPlus_BitmapDispose($bild)
    _Crypt_Startup()

    [/autoit][autoit][/autoit][autoit]

    $MD5 = _Crypt_HashData($sFile, $CALG_MD5)
    MsgBox(0, 'Hash', "MD5: " & Hex($MD5))
    _Crypt_Shutdown()
    _GDIPlus_Shutdown()

    [/autoit][autoit][/autoit][autoit]

    Func _GDIPlus_StreamImage2BinaryString($hBitmap, $sFormat = "JPG", $iQuality = 80, $bSave = False, $sFilename = @ScriptDir & "\Converted.jpg") ;coded by UEZ 2013 build 2013-09-14
    Local $sImgCLSID, $tGUID, $tParams
    Switch $sFormat
    Case "JPG"
    $sImgCLSID = _GDIPlus_EncodersGetCLSID($sFormat)
    $tGUID = _WinAPI_GUIDFromString($sImgCLSID)
    Local $tData = DllStructCreate("int Quality")
    DllStructSetData($tData, "Quality", $iQuality) ;quality 0-100
    Local $pData = DllStructGetPtr($tData)
    $tParams = _GDIPlus_ParamInit(1)
    _GDIPlus_ParamAdd($tParams, $GDIP_EPGQUALITY, 1, $GDIP_EPTLONG, $pData)
    Case "PNG", "BMP", "GIF", "TIF"
    $sImgCLSID = _GDIPlus_EncodersGetCLSID($sFormat)
    $tGUID = _WinAPI_GUIDFromString($sImgCLSID)
    Case Else
    Return SetError(1, 0, 0)
    EndSwitch
    Local $hStream = _WinAPI_CreateStreamOnHGlobal() ;http://msdn.microsoft.com/en-us/library/ms864401.aspx
    If @error Then Return SetError(2, 0, 0)
    _GDIPlus_ImageSaveToStream($hBitmap, $hStream, DllStructGetPtr($tGUID), DllStructGetPtr($tParams))
    If @error Then Return SetError(3, 0, 0)
    _GDIPlus_BitmapDispose($hBitmap)
    Local $hMemory = _WinAPI_GetHGlobalFromStream($hStream) ;http://msdn.microsoft.com/en-us/library/aa911736.aspx
    If @error Then Return SetError(4, 0, 0)
    Local $iMemSize = _MemGlobalSize($hMemory)
    If Not $iMemSize Then Return SetError(5, 0, 0)
    Local $pMem = _MemGlobalLock($hMemory)
    $tData = DllStructCreate("byte[" & $iMemSize & "]", $pMem)
    Local $bData = DllStructGetData($tData, 1)
    _WinAPI_ReleaseStream($hStream) ;http://msdn.microsoft.com/en-us/library/…3(v=vs.85).aspx
    _MemGlobalFree($hMemory)
    If $bSave Then
    Local $hFile = FileOpen($sFilename, 18)
    If @error Then Return SetError(6, 0, $bData)
    FileWrite($hFile, $bData)
    FileClose($hFile)
    EndIf
    Return $bData
    EndFunc ;==>_GDIPlus_StreamImage2BinaryString

    [/autoit]

    Gruß,
    UEZ

    Auch am Arsch geht ein Weg vorbei...

    ¯\_(ツ)_/¯

    3 Mal editiert, zuletzt von UEZ (18. November 2013 um 00:04)

  • Hi,

    Zitat

    Das ist aber des eigentliche Problem da er das Fenster nicht erkennt werden die Pixel die eigentlich vom Fenster verdeckt sind genommen anstelle die des WPF Fensters (also er nimmt die Pixel des hinterm dem WPF liegenden Desktops).

    probiere mal, das WPF-Fenster direkt anzusprechen uber den DC.
    Also statt

    [autoit]

    Local $hDC = _WinAPI_GetDC(0) ;HDC Desktop

    [/autoit][autoit]

    Local $hwnd = WinGetHandle ( "Dein Fenstertitel")
    Local $hDC = _WinAPI_GetDC($hwnd) ;HDC Fenster

    [/autoit]

    Btw., läuft dein AutoItscrit "in" der VM oder "draussen"?

    Wenn es funktioniert über Printscreen und du dort einen Bitmap-Handle erhälst, dann kannst du doch diese Daten nutzen...


    UEZ,
    ScreenCaptureCapture() blittet auch nur den DC, s. Funktion.

  • Hallo Zusammen,

    nochmal vielen vielen dank für die Mühe die ihr euch macht. Ich habe gerade mit der Arbeit recht viel um die Ohren daher kann ich mich nicht so intensiv an das Thema setzen. Ich befürchte das geht erst wieder am Freitag, daher habe ich auch noch nicht viel probieren können.

    Ich habe Windows nur in der VM und nutze ein anderes OS. Habe es aber auch schon mal bei einem Bekannten ausprobiert und diese "Phänomen" taucht nur in der VM auf. Soweit ich herausbekommen habe liegt es an der simulierten Grafigkarte. Sobald es auf einen Host Windows OS ausserhalb der VM läuft bekomm ich auch ganz normal die Pixelwerte.


    [autoit]


    Local $hwnd = WinGetHandle ( "Dein Fenstertitel")
    Local $hDC = _WinAPI_GetDC($hwnd)

    [/autoit]

    Das geht, da bekomm ich den Handle. Die WPF kann ich auch direkt ansprechen. Ich bekomm halt nur keine Pixelwerte von ihr.

    Andy das Bild soll nicht auf die Platte gespeichert werden sondern, so mein Grundgedanke, per GDI direkt aus dem Zwischenspeicher geholt werden, um es dann zu zuschneiden und dann den Hashwert dessen nehmen -> da bekomm ich aber nur "FFFFFF" als Ergebnis vom MD5 heraus.

    Muss ich denn direkt an die Pixel heran um einen eindeutigen Haswert zu erhalten oder reicht es nicht wenn ich Einfach den GDI Wert des Bildes speichere? Oder ist im GDI nur der Handle der auf den Speicher verweist wo sich die Daten befinden? Oder Denk ich das gerade in die falsche Richtung?

  • Hi,
    du brauchst das Bild nicht "zuzuschneiden" wenn du es direkt mit der richtigen Größe und Position ausliest.
    Das was du per GDI bekommst ist ein sog. "Handle" also nichts weiter als eine Adresse, an deren Position die Beschreibung des Objektes, in diesem Fall ein Bild , liegt.
    Dieses Handle wird jedes Mal neu erstellt, ist also immer unterschiedlich und nicht vergleichbar! Ergo musst du die Pixeldaten hashen, die sind (hoffentlich) immer gleich.

    Zitat

    Ich habe Windows nur in der VM und nutze ein anderes OS.

    Weiss der Henker, welche Methoden in dieser VM genutzt werden, um Fenster darzustellen. Wenn nicht per GDI dann per DirectX...in dem Fall hast du gelitten. Was ich allerdings nicht verstehe, ist dass ein PRINTSCREEN funktioniert bzw. das Fenster anzeigt.
    Machst du den PRINTSCREEN innerhalb der VM oder ausserhalb?