Bild Schwarzweiß machen in Assembler

  • Hallo,

    Meine ersten Schritte in ASM haben Früchte getragen und ich wollte euch mein erstes "Assembler"-Powered Script präsentieren :D

    Der Assemblerpart ist wirklich rasend schnell - ca. 11ms für 1024x768 pixel.

    Danke nochmal an Andy, ohne dessen Hilfe das wohl nie zustande gekommen wäre.

    Für die interessierten habe ich auch den ASM Code beigelegt.

    LG

  • Lass uns nicht hängen, in Graustufen bitte auch noch ^^ :thumbup:

  • Wow super nice.
    Assembler macht AutoIt ja wirklich extrem schnell.
    Leider ist es nicht wirklich schwarz-weiß.
    Habe mir mal ein größeres, farbenfrohes Bild genommen, und es war nach 19 ms fertig.
    Darauf hatte ich dann ein schwarzes Bild mit einem weißen Streifen :/
    Aber bei der Mona klappts super.
    MfG. PrideRage

    Meine Projekte:
    ClipBoard Manager (beendet)
    Gutes ClipBoard Verwaltungs Programm mit nützlichen Funktionen.

    HTML Creator (beendet)
    Nützliches Tool um schnell ein eigenes HTML Dokument zu erstellen.

    4 Mal editiert, zuletzt von PrideRage (10. September 2010 um 20:26)

  • @PrideRange: Ja das wird größer, weil ich das als bmp abspeicher - kannst ja auch mal den Part beim _GDIPlus_ImageSaveToFile auf jpg oder png ändern. Das mit der Farbe kommt einfach daher, dass das Script ja nicht automatisch weiß, wie hell das bild ist (hey, jetzt hab ich ne Idee für Version 3), deswegen hat die Funktion einen Parameter "$iLight". Je höher dieser gesetzt ist, desto hellere Farben werden eingeschwärzt.

    Schau dir mal das neue Script in Post 1 an:

    [autoit]

    ;~ ;Schwarzweiß:
    ;~ ASM_BitmapBnW(@ScriptDir & "\mona-lisa.jpg", 100, 1, @ScriptDir & "\mona-lisa-bnw.bmp")

    [/autoit]

    Ändere hier mal den zweiten Parameter auf 50.

    Andy: Jetzt habe ich auch die Version mit den Graustufen fertig :)
    Alles in Post 1 ;)

  • SEuBo
    Klappt super.
    Was mich wunder ist, dass das neue SW Bild (welches 100% gut ist) 14ms berechnungszeit hatte,
    und das andere kaputte Bild brauchte 19 ms.
    Wobei man berücksichtigen muss, dass ich bei den 14 ms noch eine CPU-Intensive Radiositäts Renderung im Hintergrund
    laufen hatte.
    Also :thumbup: für dein Skript .

    Meine Projekte:
    ClipBoard Manager (beendet)
    Gutes ClipBoard Verwaltungs Programm mit nützlichen Funktionen.

    HTML Creator (beendet)
    Nützliches Tool um schnell ein eigenes HTML Dokument zu erstellen.

  • PrideRage, genau dein Beispiel ist ziemlich gut gewählt, um die Problematik bei einem Schwarzweißbild darzustellen.
    Ab welcher "Farbe" wird das Pixel weiß, bzw. schwarz?
    Genau deshalb macht es auch keinen(sehr wenig) Sinn, Bilder "ohne Kontrast" in Monochrom darzustellen!

    Mit Graustufen sieht das ganz anders aus...

    Spoiler anzeigen
    [autoit]

    #include <AssembleIt.au3>
    #include <Array.au3>
    #include <GDIPlus.au3>

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

    func _graustufen()
    _("use32") ;sollte immer eingesetzt werden!

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

    _("mov esi,dword[esp+4]") ;Startadresse Bitmapdaten (Pixel)
    _("mov ecx,dword[esp+8]") ;anzahl Pixel
    _("mov edi,21845") ;konstante, *21845/2^16 ist ungefähr 1/3
    _("xor eax,eax") ;eax=zur ermittlung des grautons = (RR+GG+BB)/3 =farbe graustufe
    _("_schleife:") ;so lange, bis ecx=0
    _("mov edx,[esi]") ;pixel laden AARRGGBB (RR+GG+BB)/3 =farbe graustufe
    _("mov al,dl") ;lowbyte (BB) vom Pixel nach lowbyte al
    _("movzx bx,dh") ;highbyte (GG) vom Pixel nach lowbyte von bx (bh ist 0)
    _("shr edx,8") ;RR ins dh schieben
    _("add ax,bx") ;BB + GG
    _("movzx bx,dh") ;highbyte (RR) vom Pixel nach lowbyte von bx (bh ist 0)
    _("add ax,bx") ;und dazuzählen dx=RR+GG+BB
    _("mul edi") ;*21845 *21845/2^16 ist ungefähr 1/3
    _("shr eax,16") ;/2^16 in al steht nun der farbcode (grauton) für RR, GG und BB
    _("movzx dx,al") ;grauton nach dl, in dh steht 0
    _("shl edx,16") ;grauton nach RR, in AA steht 0!
    _("mov dh,al") ;grauton nach GG
    _("mov dl,al") ;grauton nach BB In edx steht nun 00alalal=grauton
    _("mov [esi],edx") ;pixel schreiben
    _("add esi,4") ;4 Byte= 1 Pixel weiter
    _("loop _schleife") ;so lange, bis ecx=0
    _("ret ")
    endfunc

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

    _GDIPlus_Startup()
    $hBitmap = _GDIPlus_BitmapCreateFromFile(@ScriptDir & "\mona-lisa.jpg")
    Local $iWidth, $iHeight, $hBitmapData, $Scan, $Stride, $tPixelData, $pPixelStruct
    $iWidth = _GDIPlus_ImageGetWidth($hBitmap)
    $iHeight = _GDIPlus_ImageGetHeight($hBitmap)

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

    $hBitmapData = _GDIPlus_BitmapLockBits($hBitmap, 0, 0, $iWidth, $iHeight, BitOR($GDIP_ILMREAD, $GDIP_ILMWRITE), $GDIP_PXF32RGB)
    $Scan = DllStructGetData($hBitmapData, "Scan0")
    $Stride = DllStructGetData($hBitmapData, "Stride")
    $tPixelData = DllStructCreate("dword[" & (Abs($Stride * $iHeight)) & "]", $Scan)

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

    $ret=_AssembleIt("ptr","_graustufen","ptr", DllStructGetPtr($tPixelData), "int", $iWidth * $iHeight) ;ptr als Rückgabe, um die hexzahlen schön zu sehen
    ;MsgBox(262144,'Ergebnis _test() ', $ret)
    _GDIPlus_BitmapUnlockBits($hBitmap, $hBitmapData)
    _GDIPlus_ImageSaveToFile($hBitmap, @ScriptDir & "\ams_monalisa.bmp")

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

    ShellExecute(@ScriptDir & "\ams_monalisa.bmp")

    [/autoit]


    AssembleIt ist ja nur zum Entwickeln und debuggen der Assemblerfunktion, wenn man den Bytecode erstmal hat, ist der winproc-Call natürlich wesentlich schneller ....

    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 (22. September 2010 um 12:00)

  • Andy
    Dein Beispiel funzt auch sehr gut.

    Aber ich verstehe den Sinn von AssembleIt nicht so ganz.
    Es macht doch im prinzip dasselbe wie MemoryFuncCall(..) oder ??

    Meine Projekte:
    ClipBoard Manager (beendet)
    Gutes ClipBoard Verwaltungs Programm mit nützlichen Funktionen.

    HTML Creator (beendet)
    Nützliches Tool um schnell ein eigenes HTML Dokument zu erstellen.

  • ja klar, natürlich, reduziert den kopieraufwand aber auf eine Zeile statt auf mehrere (wenn man es öfter benutzt), der gesamte Rattenschwanz der FASM-Funktionen fällt komplett weg!

    /edit/ wenn der Code läuft, macht man sowieso einen CallWindowProcW() daraus. Wäre mal ne Massnahme, das in AssembleIt zu integrieren: die komplett fix- und fertige AutoIt-Funktion in die Zwischenablage schreiben, so dass man sie nur noch ins fertige Script kopieren muss....

    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

    2 Mal editiert, zuletzt von Andy (10. September 2010 um 21:47)

  • Noch ein kleines Beispiel, um ein "Negativ" zu erzeugen, wie es der ein- oder andere vielleicht noch von "richtigen" Fotos kennt....

    Spoiler anzeigen
    [autoit]

    #include <AssembleIt.au3>
    #include <GDIPlus.au3>

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

    Func _negativ()
    _("use32") ;sollte immer eingesetzt werden!

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

    _("mov edi,dword[esp+4]") ;Startadresse Bitmapdaten (Pixel)
    _("mov ecx,dword[esp+8]") ;anzahl Pixel, breite * hoehe

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

    _("_schleife:") ;so lange, bis ecx=0
    _("mov eax,0xFFFFFF") ;eax=0xFFFFFF
    _("sub eax,[edi]") ;jedes Byte R,G,B wird von 255 (0xFF) subtrahiert, also R=255-R G=255-G B= 255-B
    _("stosd") ;Pixel schreiben [edi]=eax und danach edi=edi+4
    _("loop _schleife") ;ecx=ecx-1 so lange, bis ecx=0, solange ecx<>0 sprung zu _schleife
    _("ret ")

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

    EndFunc ;==>_negativ

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

    ;Bild öffnen und Startpointer auf die "Pixel" bestimmen
    _GDIPlus_Startup()
    $hBitmap = _GDIPlus_BitmapCreateFromFile(@ScriptDir & "\mona-lisa.jpg")
    Local $iWidth, $iHeight, $hBitmapData, $Scan, $Stride, $tPixelData, $pPixelStruct
    $iWidth = _GDIPlus_ImageGetWidth($hBitmap)
    $iHeight = _GDIPlus_ImageGetHeight($hBitmap)

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

    $hBitmapData = _GDIPlus_BitmapLockBits($hBitmap, 0, 0, $iWidth, $iHeight, BitOR($GDIP_ILMREAD, $GDIP_ILMWRITE), $GDIP_PXF32RGB)
    $Scan = DllStructGetData($hBitmapData, "Scan0")
    $Stride = DllStructGetData($hBitmapData, "Stride")
    $tPixelData = DllStructCreate("dword[" & (Abs($Stride * $iHeight)) & "]", $Scan) ;struct für die Pixel

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

    $ret=_AssembleIt("ptr","_negativ","ptr", DllStructGetPtr($tPixelData), "int", $iWidth * $iHeight) ;ptr als Rückgabe hat keinen besonderen Grund

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

    _GDIPlus_BitmapUnlockBits($hBitmap, $hBitmapData)
    _GDIPlus_ImageSaveToFile($hBitmap, @ScriptDir & "\ams_monalisa.bmp")

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

    ShellExecute(@ScriptDir & "\ams_monalisa.bmp")

    [/autoit]


    und als Bytecode ohne FASM-UDF

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>
    #include <GDIPlus.au3>

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

    Global $tCodeBuffer = DllStructCreate("byte[19]") ;Speicher für den assemblercode belegen
    DllStructSetData($tCodeBuffer, 1, "0x8B7C24048B4C2408B8FFFFFF002B07ABE2F6C3") ;assemblercode in speicher schreiben

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

    _GDIPlus_Startup()
    $hBitmap = _GDIPlus_BitmapCreateFromFile(@ScriptDir & "\mona-lisa.jpg")
    Local $iWidth, $iHeight, $hBitmapData, $Scan, $Stride, $tPixelData, $pPixelStruct
    $iWidth = _GDIPlus_ImageGetWidth($hBitmap)
    $iHeight = _GDIPlus_ImageGetHeight($hBitmap)

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

    $hBitmapData = _GDIPlus_BitmapLockBits($hBitmap, 0, 0, $iWidth, $iHeight, BitOR($GDIP_ILMREAD, $GDIP_ILMWRITE), $GDIP_PXF32RGB)
    $Scan = DllStructGetData($hBitmapData, "Scan0")
    $Stride = DllStructGetData($hBitmapData, "Stride")
    $tPixelData = DllStructCreate("dword[" & (Abs($Stride * $iHeight)) & "]", $Scan)

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

    $ret=DllCall("user32.dll", "ptr", "CallWindowProcW", "ptr", DllStructGetPtr($tCodeBuffer),"ptr",DllStructGetPtr($tPixelData),"int",$iWidth*$iHeight,"int",0,"int",0)

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

    _GDIPlus_BitmapUnlockBits($hBitmap, $hBitmapData)
    _GDIPlus_ImageSaveToFile($hBitmap, @ScriptDir & "\ams_monalisa.bmp")

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

    ShellExecute(@ScriptDir & "\ams_monalisa.bmp")

    [/autoit]

    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

    Einmal editiert, zuletzt von Andy (23. Oktober 2010 um 12:20)

  • [autoit]

    FasmAdd($hFasm, "mov edx, 0") ; Register komplett leeren
    FasmAdd($hFasm, "mov ebx, 0") ; Register komplett leeren
    FasmAdd($hFasm, "mov ecx, 0") ; Register komplett leeren

    [/autoit]

    Als kurze Frage ohne die UDF genau zu kennen, bzw. mich damit auseinander gesetzt zu haben:

    Geht es nicht schneller wenn man das Register mit sich selber Xor'd? Oder gibts XOR nicht bei der UDF irgendwie?

  • Hi,
    die "UDF" ist ein vollwertiger Assembler, ich habe bis jetzt keinen (wichtigen) Befehl vermisst. Daß irgendwelche prozessorspezifischen Spezialitäten (u.a aus SSE4.0) erst vor kurzem implementiert wurden ist nicht erwähnenswert.

    Zitat

    Geht es nicht schneller wenn man das Register mit sich selber Xor'd? Oder gibts XOR nicht bei der UDF irgendwie?

    Ob ein XOR schneller ist? Kommt auf den Code insgesamt an! Sagen könnten das ggf. Analysten und vielleicht eine Handvoll guter Compilerbauer. Da moderne Prozessoren ihre Befehls-Pipelines selbstständig verwalten/umsortieren, Sprungvorhersagen nach bestimmten Algorithmen "schätzen" und selbstständig versuchen Abhängigkeiten aufzulösen hängt die Geschwindigkeit nicht an einem Befehl, sondern an einer kompletten Befehlskette.
    Will sagen, wenn du in einem Loop permanente Stalls hast, ist es sch***egal, ob das XOR einen halben oder einen viertel-"Takt" schneller ist als ein MOV. Wenn der Prozessor hunderte von Wartetakten einfügen muss weil der Programmierer "gegen" den Prozessor arbeitet, ist der schnellste Befehl für die Katz...
    Mach doch einfach mal eine "laaaange" Schleife und probiers aus. Aber ups, je länger die Schleife desto eher schlägt das Betriebssystem zu und interrruptet fleissig...
    Viel Spass beim Programmieren eines Timers, der "Takte" zählen kann. Diese Timer bzw. Programme gibt es, die machen aber m.E. nur dort Sinn, wo es wirklich um GELD geht, also um Berechnungen auf extrem teuren Maschinen. Ruf mal beim CERN an (ich habe das mal spasseshalber vor einigen Jahren gemacht, weil einer meiner ehemaligen Profs wieder dort arbeitet) und frag mal nach, was eine Stunde auf einer 200-Prozessor-Maschine mit 5 Terabyte RAM kostet....

    Für den "Otto-Normal-Programmierer" ist der Unterschied definitiv nicht feststellbar (beim Tippen dieses Textes "schläft" mein Prozessor mehr als das er arbeitet). Ich schliesse mich aber der Meinung eines Softwareentwicklers/Testers an, der mir einmal sagte:" Ein MOV EAX,0 ist für jeden ohne Nachdenken (auch unbewußt) nachvollziehbar und stört nicht den "Fluß" beim Lesen des Codes. Selbst wenn ein XOR EAX,EAX schneller WÄRE (was es zu beweisen gilt) ist dem MOV dennoch der Vorzug zu geben!"

  • Zitat

    Debugger jedoch machen immer xor

    Macht das der Debugger (weils angeblich "professioneller" aussieht) oder steht das im Code?
    Der Compiler macht aus dem XOR ein MOV (weils eh keiner merkt), macht aber nix, weil der Debugger ja aus dem (noobigen) B800000000 ein 31C0 macht und so ganze 4 Bytes "spart".... :rofl:

    *whispermode ON*
    Die 4 "eingesparten" Bytes des XOR machen dann Sinn, wenn man anfängt, in den Eingeweiden des Prozessors zu optimieren oder wenn man aus welchen Gründen auch immer 4 Bytes im Code einsparen MUSS. Für die 08/15 Anwendung hat mir noch keiner nachweisen können, daß ein MOV statt des XOR den Code zum Schneckentempo verurteilt....
    *whispermode OFF*
    Was mich an den Witz erinnert vom kleinen Jungen, der mit seinem Dreirad gemütlich vor der Strassenbahn herfährt. Der Strassenbahnfahrer klingelt wie wild, irgendwann reichts ihm und er brüllt aus dem Fenster:" Hey, Kleiner! Kannst du nicht ein bisschen schneller fahren?" Darauf dreht sich der Junge um, grinst den Strassenbahnfahrer an und sagt: "Ich schon.....aber du nicht!" ;)

  • Naja wenn man sich den Assembler Code einer exe Anwendung anschaut, sieht man zumindestens bei OllyDbg ein XOR um ein Register zu leeren anstatt ein mov eax, 0.

    Hm den Opcode vom Xor hab ich mir sowieso noch nie angeschaut... Vielleicht macht es ein Debugger aus dem Grund, dass es leichter ist zu lesen, wenn man sich den Speicher direkt anschaut?

    @"Witz": Ders gut :D

  • Zitat

    Naja wenn man sich den Assembler Code einer exe Anwendung anschaut,

    Falsch, den disassemblierten Code einer Anwendung! Und genau das ist das Problem! Die "gängigen" Compiler verwenden natürlich den XOR, weil der Opcode 4 Byte kürzer ist, damit hat es sich schon. Wenn du einem Assembler sagst, er soll ein MOV kodieren, dann macht der das auch in der Regel! Assembler optimieren nicht, Compiler schon.....

  • Jaa, ich glaube aber du weißt was ch meine :D
    Und es ist besser nur 4 Byte Opcode zu lesen, 1. weil man Infos viel leichter rauslesen kann (wenn man die API kennt) und 2. weil sie so 8 Byte in dem Speicher zu lesen nicht schön macht.
    Aber eine Frage: Anscheinend braucht xor ja weniger Byte, benötigt aber dieselbe Anzahl an Parameter wie mov, warum ist xor kürzer?
    Ich wieß nicht ob es bei einem x86 Assembler auch einen "Program Counter" gibt, aber wenn ja hat sichs mit der Frage nahc den 4 Byte mehr :)

    Ich programmiere zurzeit nämlich nur ATmega8 (Microcontroller) Assembler, deshalb weiß ich nicht wies bei anderen ist. XOR gibts bei ATmega Assembler z.B. nicht ;)

  • Zitat

    Aber eine Frage: Anscheinend braucht xor ja weniger Byte, benötigt aber dieselbe Anzahl an Parameter wie mov, warum ist xor kürzer?

    Das ist "hardcodiert"... Im Prinzip kannst du mit 2-Byte-Opcodes um die 255x255 Befehle "darstellen". So viele gibt es aber garnicht, also gehen die Prozessorhersteller hin, und "konstruieren" oft benutzte Befehle in 2- oder 3-Byte Opcodes.
    So kommt es auch vor, dass einige unterschiedliche Befehle identische Opcodes haben.
    Da es etwas übel wäre, die Opcodes für 65000 Befehle in eine Liste zu schreiben, werden die Opcodes Bitweise nach bestimmten Regeln ermittelt. Das ist schweres Futter, das richtige für Nerds zum austoben^^
    U.a in den Intel-Handbüchern findest du seitenlange Erklärungen und Tabellen, wie sich Opcodes zusammensetzen. Aber "brauchen" tut das kein Mensch, obwohl ich mal einen kannte, der diese "Regeln" grösstenteils auswendig konnte. Damit ist man z.B. in der Lage, ohne Compiler/Assembler Programme zu schreiben^^...wers braucht...
    Aber jemand, der einen Assembler/Compiler erstellen möchte, sollte sich mit diesem Thema auskennen....

    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

    Einmal editiert, zuletzt von Andy (22. September 2010 um 08:03)