[Tutorial] Screenshots aufnehmen

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • [Tutorial] Screenshots aufnehmen

    Einleitung


    Eine Mischung aus Tutorial und Coding-Tagebuch
    Nachdem ich einige Monate lang nur kleinere Programme geschrieben haben möchte ich nun mit meinem ersten größeren Projekt anfangen.
    Das Ziel ist es ein Tool zu programmieren, mit dem man man schnell Bilder oder Screenshots auswählen und bei einem Online-Imagesharingdienst (in diesem Fall Imgur.com) hochladen kann, sodass am Ende ein Link zum Einbetten in Forenposts herauskommt.
    So etwas ist natürlich praktisch wenn man Tutorials oder ähnliches schreiben will (YO DAWG)

    Um mich selbst zu motivieren und um vielleicht dem ein oder anderen Cocoa Anfänger Tipps oder Anregungen zu geben möchte ich meinen Entwicklungsfortschritt in diesem Tutorial dokumentieren.
    Dazu wird das Hauptprojekt in mehrere kleine Unteraufgaben eingeteilt, z.B.
    • Screenshots aufnehmen
    • Bilder hochladen
    Wenn erforderlich kommen dann zu den einzelnen Aufgaben nocheinmal Unterthemen hinzu (XML-Parsing zum Thema "Bilder hochladen").

    Das Ganze soll sich an etwas Fortgeschrittenere Anfänger richten, die schon in der Lage sind eigenständig z.B. Xcode-Projekte zu beginnen oder Interfaceelemente anzuordnen und zu verknüpfen.


    Ich würde mich über Feedback freuen, zum Einen von Anfängern, ob meine Anleitungen verständlich sind und zum Anderen von Verbesserungsvorschläge zu meinem Code erfahreneren Programmieren :D
  • Teil 1: Screenshots aufnehmen

    Teil 1: Screenshots aufnehmen


    Wie schon in der Einleitung erwähnt möchte ich das Gesamtprojekt in einzelne Teilabschnitte gliedern.

    Hierzu öffnen wir ein neues Projekt in Xcode (Cocoa Application, nicht document based und mit aktivem Automatic Reference Counting) und nennen es zum Beispiel "ScreenshotTest"

    Ziel dieses Abschnittes soll keine komplette Anwendung sein, sondern nur Code um ein bestimmtes Fenster zu finden und seinen Inhalt abzufotografieren.

    Benutzt werden CGWindow-Funktionen [Link zur Dokumentation]


    Mithilfe der Funktion "CGWindowListCreateImage" kann man den Inhalt eines beliebigen Fensters als CGImageRef darstellen.

    Quellcode

    1. CGImageRef windowImage = CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, windowId, kCGWindowImageBoundsIgnoreFraming);

    Dann überprüfen wir mögliche Fehler

    Quellcode

    1. // Überprüfen ob das Bild richtig erstellt wurde (breiter als 1px)
    2. if(CGImageGetWidth(windowImage) <= 1)
    3. CGImageRelease(windowImage); else NSLog(@"Erstellen des Screenshots Fehlgeschlagen");

    Und legen schließlich ein NSImage mit dem Inhalt der CFImageRef an
    Dei Funktion "CGImageRelease" wird hierbei trotz ARC benötigt, da es sich nicht um einen normalen Datentypen handelt.

    Quellcode

    1. NSBitmapImageRep *bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage: windowImage];
    2. NSImage *image = [[NSImage alloc] init];
    3. [image addRepresentation: bitmapRep];
    4. CGImageRelease(windowImage);

    Mit dem wir z.B. einen Imageview füllen

    Quellcode

    1. [screenshotPreview setImage:image];

    Der experimentierfreudige Leser kann jetzt -nennen wir es FDD, Fail Driven Development- betreiben, im Interface Builder einen "Image Well" und ein "Text Field" in das Fenster ziehen und diese in der Headerdatei deklarieren und verknüpfen

    Quellcode

    1. @interface AppDelegate : NSObject <NSApplicationDelegate>
    2. {
    3. IBOutlet NSImageView *screenshotPreview; IBOutlet NSTextField *windowIDField;
    4. }
    5. @property (assign) IBOutlet NSWindow *window;
    6. @end


    Das TextFiled als Action namens "refreshScreenshot" verknüpfen und den Folgenden Code in die entstandene Funktion in der AppDelegate.m einfügen

    Quellcode

    1. CGImageRef windowImage = CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, windowIDField.integerValue, kCGWindowImageBoundsIgnoreFraming);
    2. Überprüfen ob das Bild richtig erstellt wurde (breiter als 1px) if(CGImageGetWidth(windowImage) <= 1)
    3. CGImageRelease(windowImage); else NSLog(@"Erstellen des Screenshots Fehlgeschlagen");
    4. NSBitmapImageRep *bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage: windowImage];
    5. NSImage *image = [[NSImage alloc] init];
    6. [image addRepresentation: bitmapRep];
    7. [screenshotPreview setImage:image];


    Dies ist im Grunde genommen alte Code, nur das statt der vorher nicht deklarierten windowId der Integerwert des Textfeldes verwendet wird.
    Ist ales richtig kopiert und verknüpft sollte sich beim Klick auf den Build-Button ein Fenster mit Textfeld und Bildanzeige öffnen.
    Gebt ihn nun eine Zahl in das Textfeld ein und bestätigt mit Enter sollte der Inhalt des dazugehörigen Fensters angezeigt werden oder auch nicht, denn ich nenne das Ganze nicht umsonst FDD ^^
    die einzelnen Fenster sind nicht etwa einfach von 1 bis 10 durchnummeriert und die Fenster sind nicht unbedingt die, die ihr auf eurem Desktop seht... vllt. stürzt das Programm auch einfach ab weil ihr die Id eines nichtexistenten Fensters gewählt habt 8o

    Und Jetzt?

    +Wir haben ein einfaches Tool geschrieben um anhand der WindowId den Inhalt eines Fensters als NSImage anzuzeigen
    -Wir müssen einen Weg finden die WindowId eines bestimmten Fensters zu finden ?(
    Hier ist nocheinmal das bisherige Projekt zum Download (heißt nur anders) ChoboScreen_Screenshot.zip

    Der nächste Schritt


    Um jetzt an die Fenster zu kommen, die uns interessieren, erstellen wir mit

    Quellcode

    1. CGWindowListOption opt = kCGWindowListOptionOnScreenOnly|kCGWindowListExcludeDesktopElements;
    2. CFArrayRef windowArrayRef = CGWindowListCopyWindowInfo(opt,kCGNullWindowID);


    eine Liste aller richtigen Anwendungsfenster, "opt" dient hierbei als Filter

    In dem ArrayRef befindet sich nun für jedes Fenster in DictionaryRef mit Informationen zum betreffendem Fenster.
    Diese lassen wir uns wie folgt ersteinmal auflisten

    Quellcode

    1. for(int i=0;i<CFArrayGetCount(windowArrayRef);i++)
    2. {
    3. CFDictionaryRef windowInfo = CFArrayGetValueAtIndex(windowArrayRef, i);
    4. CFStringRef windowNameRef = CFDictionaryGetValue(windowInfo, kCGWindowName);
    5. CFStringRef windowIDRef = CFDictionaryGetValue(windowInfo, kCGWindowNumber);
    6. NSString *windowName = (NSString*)CFBridgingRelease(windowNameRef);
    7. NSString *windowID = (NSString*)CFBridgingRelease(windowIDRef);
    8. NSLog(@"Fenster %@ hat die ID %@",windowName,windowID);
    9. }


    Mit einer for-Schleife gehen wir die gesamte Liste durch, erstellen eine DictionaryRef des aktuellen Fensters und lesen dessen Namen und ID aus.
    Funktionen wie "CFArrayGetValueAtIndex" sind hierbei notwendig, da es sich um spezielle Datentypen handelt, "CFBridgingRelease" erstellt ein normales NS-Objekt aus einem CF-Datentypen wobei das oben genannte "CFRelease" entfällt.

    Fügt man diese beiden Codestücke nun in die Funktion "applicationDidFinishLaunching" unseres Projekts ein und Starten wir diese, erscheint in der Xcode Konsole eine Liste in diesem Stil:

    2012-09-19 18:12:33.478 ChoboScreen_Screenshot[17231:303] Fenster iTunes hat die ID 1992

    2012-09-19 18:12:33.478 ChoboScreen_Screenshot[17231:303] Fenster Skype hat die ID 759

    2012-09-19 18:12:33.478 ChoboScreen_Screenshot[17231:303] Fenster Aktivitätsanzeige hat die ID 92


    Gibt man nun eine der IDs in das Textfeld ein erscheint das Fenster im Imageview


    Und Jetzt?

    +Wir können uns nun eine Liste alles wichtigen Fenster anzeigen lassen und auf deren IDs zugreifen
    -Viele Fenster sind immernoch überflüssig und das Interface ist noch unpraktisch

    Hier ist nocheinmal das bisherige Projekt zum Download (heißt nur anders/Version2) ChoboScreen_Screenshot 2.zip


    Das wars erstmal soweit... :)
    sobald ich weitergecodet habe werde ich diesen Post natürlich aktualisieren


    Ich hoffe es hat euch gefallen, scheut euch nicht Kritik oder Fragen zu posten, dashier ist immerhin mein erstes Tutorial ;)