Tag Archives: Flash

Next Generation User Interfaces How-To: Kommandozeile

cliDas Beispiel eines unterwürfigen Hühnchens (Subservient Chicken) zeigt, wie die Kommandozeile zur Kommunikation mit dem System genutzt werden kann. Eingaben mit Schlüsselwörtern wie etwas „Dance“ lassen das Hühnchen tanzen.

Die Tastatur kann zur Eingabe ganzer Kommandos in einer Art Kommandozeile (CLIs, Command Line Interfaces) dienen. Diese Anwendungen erscheinen dabei meist intelligenter als Sie sind. Es gibt zwar auch anspruchsvolle Anwendungen, die in einem beschränkten Rahmen Sprache wirklich verstehen, doch meist wird nur nach Schlüsselwörtern und Phrasen Ausschau gehalten. Für einfache Anwendungen reicht dann eine Schleife wie im folgenden Code für Flex 4:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
			   xmlns:s="library://ns.adobe.com/flex/spark" 
			   xmlns:mx="library://ns.adobe.com/flex/mx" >
 
	<fx:Script>
		<![CDATA[
			import mx.events.FlexEvent;
 
			// Führe Befehl aus
			private function executeCommand():void {
				// Wandle Befehlseingabe in Kleinbuchstaben um
				var lowercasetext:String = ti.text.toLowerCase();
				// Ermittle, ob Befehl zum Einschalten eingegeben wurde...
				if (lowercasetext.indexOf("on")>-1) {
					// ...und aktiviere Kontrollkästchen...
					cb.selected = true;
					// ... überprüfe, ob Befehl zum Ausschalten eingegeben wurde...
				} else if (lowercasetext.indexOf("off")>-1) {
					// ...und deaktiviere Kontrollkästchen...
					cb.selected = false;
				}				
			}			
 
			protected function ti_valueCommitHandler(event:FlexEvent):void {
				executeCommand();
			}
 
			protected function ti_enterHandler(event:FlexEvent):void {
				executeCommand();
			}
		]]>
	</fx:Script>
 
	<s:CheckBox x="10" y="10" label="Aktiviere mich per Kommandozeile" id="cb"/>
	<s:TextInput x="10" y="36" width="209" id="ti" 
				 enter="ti_enterHandler(event)"
				 valueCommit="ti_valueCommitHandler(event)" />
 
</s:Application>

Stereoskopisches 3D im Web mit Silverlight (oder Flash)

Normalerweise nehmen wir die Welt räumlich war, indem jedes Auge über die Netzhaut ein zweidimensionales Bild ermittelt und dann an das Gehirn sendet. Sofern zwei funktionstüchtige Augen und ein dazu passendes und entsprechend trainiertes Gehirn vorhanden sind, wird daraus ein räumlicher Sinneseindruck. 3D-Filme machen von diesem Verhalten gebrauch, indem auf irgendeine Art und Weise diese zwei Bilder an jeweils ein Auge gesandt werden (Stereoskopie): Für die Aufnahme werden meist spezielle Linsen, zwei Kameras oder die Verschiebung einer Kamera genutzt. Beim Test hilft fertiges Bildmaterial wie Muttyan’s Stereo Galleries.

3D_cameras In McGyver-Manier haben wir aus zwei alten Webcams (mit gruseliger Bildqualität) und einem Reststück Parkettboden eine Art 3D-USB-Kamera gebaut: Wichtig hierbei ist, dass die Linsen möglichst genau 6,5 cm auseinanderstehen, da dies dem menschlichen Seheindruck am nächsten kommt (größer heißt, dass alles kleiner wirkt und bei kleinerem Abstand wirkt alles größer). Nützlich ist übrigens bei dieser Handwerkskunst, dass die Kameras zueinander gedreht werden können, um einen gemeinsamen Punkt im Raum besser zu fokussieren (so wie das unsere Augen ja auch machen): Dies hilft ein wenig bei der Optimierung des Bildes. Die Qualität einer solchen Lösung ist trotzdem nicht vergleichbar mit den Verfahren bei modernen Filmen wie Avatar und darunter leidet auch die räumliche Wahrnehmung. Bei Avatar wurde übrigens mit zwei beweglichen Objektiven/Kameras im 6,5 cm Abstand gearbeitet, die sich zueinander wie das menschliche Auge drehen und so Punkte fokussieren können.

Es gibt eine Reihe von Verfahren, um dem rechten und linken Auge jeweils unterschiedliche Bilder zu präsentieren. Günstig und qualitativ halbwegs erträglich ist die Farbanaglyphentechnik. Dabei werden das rechte und linke Bild übereinander projiziert und jeweils eingefärbt (oft in Komplementärfarben wie Rot und Grün). Über eine spezielle Brille mit Farbfiltern wird auf jedem Auge dann ein Bild ausgelöscht. Heutzutage gängig ist die Kombination Rot und Cyan, die sich auch für Werbemaßnahmen und Comics eignet (entsprechende Brillen gibt es günstig bei Ebay). Da dabei ohnehin viele Farbinformationen verloren gehen, tendieren viele Produzenten zu Schwarzweiß – außerdem neigt diese Technik zu Geisterbildern, wenn Einfärbung und Farbfilter nicht ganz genau übereinstimmen. Aufwendigere Verfahren wie die Polarisationstechnik helfen die Geisterbilder zu vermeiden und arbeiten auch gut mit farbigen Inhalten. Das Einfärben der Inhalte gelingt mit Bildbearbeitungsprogrammen (es gibt auch darauf spezialisierte Programme) oder im Falle von Flash und Silverlight auch zur Laufzeit über Pixelshader. Bei Flash kommt dafür die Sprache Hydra und das Werkzeug Pixelbender zum Einsatz, Silverlight nutzt HSHL (High Level Shading Language) und das DirectX SDK. Eine Erleichterung ist die kostenlose Click-Once Anwendung Shazzam.

Der Folgende Effekt namens ColorChannelsEffect kümmert sich um die Umwandlung eines Bildes in Graustufen und das Variieren der Kanäle für Rot, Grün, Blau und die Transparenz (Alpha). Shazzam erzeugt automatisch den benötigten Code, so dass der Effekt dann direkt in Silverlight (oder auch WPF) genutzt werden kann:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// a shader is an algorithm that is compiled
// and loaded into the Graphics Processor Unit (GPU)
// this algorithm is run once, for every pixel in the input image
// GPUs are efficient parallel  processors and will run
// your algorithm on thousands of pixels at a time
 
/// <class>ColorChannelsEffect</class>
/// <description>An effect that turns the input into a grayscale image first and than multiplies all four channels with custom values (e.g. for anaglyph images).</description>
 
//-----------------------------------------------------------------------------------------
// Shader constant register mappings (scalars - float, double, Point, Color, Point3D, etc.)
//-----------------------------------------------------------------------------------------
 
/// <summary>Change the ratio between the Red channel</summary>
/// <minvalue>0/minValue&gt;
/// <maxvalue>1</maxvalue>
/// <defaultvalue>1</defaultvalue>
float RedRatio : register(c0);
 
/// <summary>Change the ratio between the Blue channel </summary>
/// <minvalue>0/minValue&gt;
/// <maxvalue>1</maxvalue>
/// <defaultvalue>1</defaultvalue>
float BlueRatio : register(c1);
 
/// <summary>Change the ratio between the Green channel</summary>
/// <minvalue>0/minValue&gt;
/// <maxvalue>1</maxvalue>
/// <defaultvalue>1</defaultvalue>
float GreenRatio : register(c2);
 
/// <summary>Change the ratio between the Alpha channel</summary>
/// <minvalue>0/minValue&gt;
/// <maxvalue>1</maxvalue>
/// <defaultvalue>1</defaultvalue>
float AlphaRatio : register(c3);
 
// input image
sampler2D input : register(s0);
 
// this is the entry point for the shader
// the return type is float4
// float4 contains four float values which we 
// can think of as color of a pixel
// (alpha, red, green, blue)
float4 main(float2 locationInSource : TEXCOORD) : COLOR
{
 
  // create a variable to hold our color
  float4 color;
 
  // get the color of the current pixel
  // tex2D is a HLSL function
  // 1st arg is a bitmap (called a texture in HLSL)
  // input: the incoming image, passed in from the GPU register (s0) 
  // 
  // 2nd arg is a locator for the pixel, this is normalized to range 0..1 
 
  // tex2D takes our sample input, gets a pixel at the current x,y location
  // and returns the color of the existing pixel, which means that the color is not altered
  color = tex2D( input, locationInSource.xy);
 
  // color has four value that we can change
  // color.r, color.g, color.b, color.a
 
  // get rgb values only
  float3 rgb = color.rgb;
  // create greyscale pixel via dot multiplication
  // (the effective luminance of a pixel is calculated 
  // with the following formula: 
  // 0.3 * red + 0.59 * green + 0.11 * blue);
  float3 luminance = dot(rgb, float3(0.30, 0.59, 0.11));
  // convert rgb to four channel pixel including alpha 
  color = float4(luminance, color.a);
 
  // values are normalized, so 0 is no color, 1 is full color
  color.r= color.r * RedRatio;
  color.g= color.g * GreenRatio;
  color.b= color.b * BlueRatio;
  color.a= color.a * AlphaRatio;
 
  return color;
 
}

Für die Nutzung dieses Effekts müssen die Klassendatei für ColorFiltersEffect.cs (bzw. das VisualBasic-Pendant) in das Projekt kopiert und der ColorFiltersEffect.ps als Ressource eingebunden werden (ColorFiltersEffect.fx ist nur der Quellcode des Filters und im Silverlight-Projekt nicht erforderlich). Ein kleiner Tipp am Rande: In der ColorFiltersEffect.cs-Datei muss vermutlich die pixelShader.UriSource angepasst werden: Das Beispielprojekt nutzt den Namensraum und den Assembly-Namen StereoscopeImage und der Effekt liegt dort im Verzeichnis Effects, so dass die Anweisung mit der UriSource dann so aussieht:

1
pixelShader.UriSource = new Uri("/StereoscopeImage;component/Effects/ColorChannelsEffect.ps", UriKind.Relative);

Die eigentliche Anwendung besteht aus zwei Bildern, die über die Effekte jeweils unterschiedlich eingefärbt werden. Da eine Rot-Cyan-Brille zum Einsatz kommt, müssen für das linke Auge die Kanäle Grün und Blau und für das rechte Auge die Kanäle Rot und Alpha ausgeblendet werden. Bitte beachten Sie, dass die Bilder im Beispiel nur per URL referenziert werden und das Projekt darum als OOB-Anwendung mit erweiterten Rechten läuft – natürlich können Sie auch andere Bilder verwenden und direkt in das Projekt einbinden.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<UserControl x:Class="StereoscopeImage.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:effects="clr-namespace:Biz.Wolter.Effects"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
 
    <Grid x:Name="LayoutRoot" Background="White">
        <!-- 
            Due to legal issues I did not include the original image. 
            You can either use the URL in a trusted OOB application 
            or just download free images yourself.
        -->
        <Image x:Name="leftImage" Source="http://www.stereomaker.net/galleries/wien/lr/20090921011759_l.jpg" >
            <Image.Effect>
                <effects:ColorChannelsEffect RedRatio="1" GreenRatio="0" BlueRatio="0" AlphaRatio="1" />
            </Image.Effect>
        </Image>
        <Image x:Name="rightImage" Source="http://www.stereomaker.net/galleries/wien/lr/20090921011759_r.jpg" >
            <Image.Effect>
                <effects:ColorChannelsEffect RedRatio="0" GreenRatio="1" BlueRatio="1" AlphaRatio="0" />
            </Image.Effect>
        </Image>
    </Grid>
 
</UserControl>

Das war es dann schon mit Schritt eins. Sollte das funktionieren, dann kann nun der Versuch unternommen werden, Bewegung ins Spiel zu bringen: In diesem Fall über zwei Kameras, die das linke und rechte Bild aufzeichnen.
Dafür benötigen wir anstelle der Bilder zwei Rechtecke als Container für einen VideoBrush (dieser stellt dann das Video dar) sowie ein paar Bedienelemente zum Auswählen der Kameras und zum start der Aufzeichnung.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<UserControl x:Class="StereoscopeVideo.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:effects="clr-namespace:Biz.Wolter.Effects"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
 
    <Grid x:Name="LayoutRoot" Background="White">
 
        <Rectangle x:Name="leftImage" Stroke="Black">
            <Rectangle.Effect>
                <effects:ColorChannelsEffect RedRatio="1" GreenRatio="0" BlueRatio="0" AlphaRatio="1" />
            </Rectangle.Effect>
        </Rectangle>
        <Rectangle x:Name="rightImage" Stroke="Black">
            <Rectangle.Effect>
                <effects:ColorChannelsEffect RedRatio="0" GreenRatio="1" BlueRatio="1" AlphaRatio="0" />
            </Rectangle.Effect>
        </Rectangle>
 
        <ComboBox DisplayMemberPath="FriendlyName" Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="leftDeviceComboBox" VerticalAlignment="Top" Width="120" />
        <ComboBox DisplayMemberPath="FriendlyName" Height="23" HorizontalAlignment="Left" Margin="12,41,0,0" Name="rightDeviceComboBox" VerticalAlignment="Top" Width="120" />
        <Button Content="Start" Height="23" HorizontalAlignment="Left" Margin="12,70,0,0" Name="startButton" VerticalAlignment="Top" Width="75" Click="startButton_Click" />
 
    </Grid>
 
</UserControl>

Innerhalb der Code-Behind-Datei werden die VideoBrushes mit dem Video-Signal befüllt, sobald der Anwender die Start-Schaltfläche klickt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Collections.ObjectModel;
 
namespace StereoscopeVideo
{
    public partial class MainPage : UserControl
    {
 
        private CaptureSource leftSource;
        private CaptureSource rightSource;
        private VideoBrush leftVideoBrush;
        private VideoBrush rightVideoBrush;
 
        public MainPage()
        {
            InitializeComponent();
 
            Loaded += new RoutedEventHandler(mainPage_Loaded);
        }
 
        private void mainPage_Loaded(object sender, RoutedEventArgs e)
        {
            ReadOnlyCollection<VideoCaptureDevice> devices = CaptureDeviceConfiguration.GetAvailableVideoCaptureDevices();
 
            // set left device collection
            leftDeviceComboBox.ItemsSource = devices;
            // create left capture source (capturing the video)
            leftSource = new CaptureSource();
            // create left video brush (displaying the video)...
            leftVideoBrush = new VideoBrush();
            leftVideoBrush.Stretch = Stretch.Uniform;
            // ...and use this brush to fill the corresponding ui-control
            leftImage.Fill = leftVideoBrush;
 
            rightDeviceComboBox.ItemsSource = devices;
            rightSource = new CaptureSource();
            rightVideoBrush = new VideoBrush();
            rightVideoBrush.Stretch = Stretch.Uniform;
            rightImage.Fill = rightVideoBrush;
        }
 
        private void startButton_Click(object sender, RoutedEventArgs e)
        {
            if (leftDeviceComboBox.SelectedIndex > -1 && rightDeviceComboBox.SelectedIndex > -1)
            {
                startButton.IsEnabled = false; // for testing purpose only one start possble so far
                startCapturing();
            }
            else
            {
                MessageBox.Show("Please choose two input devices first!", "Alert", MessageBoxButton.OK);
            }
        }
 
        private void startCapturing()
        {
 
            leftSource.VideoCaptureDevice = leftDeviceComboBox.SelectedItem as VideoCaptureDevice;
            rightSource.VideoCaptureDevice = rightDeviceComboBox.SelectedItem as VideoCaptureDevice;
 
            if (CaptureDeviceConfiguration.AllowedDeviceAccess || CaptureDeviceConfiguration.RequestDeviceAccess())
            {
                leftSource.Start();
                leftVideoBrush.SetSource(leftSource);
 
                if (leftSource.VideoCaptureDevice != rightSource.VideoCaptureDevice)
                {
                    rightSource.Start();
                    rightVideoBrush.SetSource(rightSource);
                }
                else
                {
                    MessageBox.Show("Only one innput device does not really make sense for this 3D application!", "Alert", MessageBoxButton.OK);
                    rightVideoBrush.SetSource(leftSource);
                }
            }
 
        }
    }
}

Das war es schon. Beide Silverlight-4-Projekte gibt es hier für Visual Studio 2010 oder Expression Blend 4 als ZIP zum Download. Viel Spaß damit…
Das Projekt ist nun bei Codeplex gehostet. Die aktuellen Quellen für Visual Studio 2010 oder Expression Blend 4 finden sich unter http://stereoscopy.codeplex.com/. Viel Spaß damit…

Next Generation User Interfaces How-To: Tastatureingabe

Eine der gängigsten Interaktionsmöglichkeiten ist die Tastatureingabe. In einem kleinen Überblick möglicher Formen der Mensch-Maschine-Interaktion darf diese darum nicht fehlen. Zumal die Tastatureingabe für mehr als nur das Ausfüllen von Textfeldern dienen kann. Tastatureingaben sind beispielsweise zum Aufruf eines Kommandos nützlich, etwas für Spiele oder als Abkürzung für gängige Operationen in Programmen wie Apfel/Strg+Z zum Rückgängigmachen. Mehr dazu und weitere ungewöhnlichen Interaktiosmöglichketen mit der Tastatureingabe gibt es in einem späteren Text zum Thema “Komandozeile”.

focusVeränderungen an dem HTML-Rahmen müssen im Flex- oder Flash-Builder in der index.template.html-Datei vorgenommen werden. Die fertige HTML-Seite wird dann daraus generiert.

Doch auch bei der Tastatureingabe gibt es kleinere Gemeinheiten wie etwa den Fokus. Denn normalerweise erhält nur diejenige Anwendung mit dem aktuellen Fokus Kenntnis über einen Tastendruck (und auch nur dann, wenn der Browser diese Tastenkombination nicht vorbelegt hat und abfängt). Bei Webanwendungen kann man dies durch die folgende JavaScript-Anweisung erzwingen: document.getElementById(“FlashContentID“).focus(); Für Flex-Anwendungen kann diese Angabe beispielsweise in der index.template.html-Datei erfolgen, die als Vorlage für den erzeugten HTML-Rahmen und die darin dann eingebettete SWF-Datei dient. Dank dem Platzhalter ${application} lässt sich die Bezeichnung der Flash-Player-Instanz leicht ermitteln und dann zum Beispiel nach erfolgreichem Laden des HTML-Dokumentes der Fokus darauf setzen. Innerhalb von Flex gelingt das Setzen des Fokus wie in der Abbildung auf einem TextInput-Steuerelement durch die Methode setFocus – nur dass dieser Fokus abgesehen vom Erscheinungsbild leider nichts nützt, wenn eben der Flash Player selbst keinen Fokus hat.

flashkeyUm Tastatureingaben in Flash über das Menü „Steuerung -> Film testen“ ausprobieren zu können, sollten im Testplayer über das Menü „Steuerung“ Tastenkürzel deaktiviert werden.

In Flash und Flex mit ActionScript 3 benachrichtigt das Ereignis KeyboardEvent.KEY_DOWN beim Drücken bzw. KeyboardEvent.KEY_UP beim Loslassen einer Taste. Das funktioniert aber auch hier nur, wenn der Fokus gesetzt ist; In der Abbildung ist deshalb die Bühne (stage) als Objekt gewählt. Das Drücken wiederholt sich jedoch auch ohne zwischenzeitliches Loslassen wenn die Taste nur lange genug gedrückt bleibt (die Wiederholrate findet sich unter Windows 7 in der Systemsteuerung unter „Tastatur -> Geschwindigkeit“). Außerdem muss man unterscheiden, ob das eigegebene Zeichen (charCode) oder die gedrückte Taste (keyCode) auf der Tastatur wichtig ist, also ob auch die Großundkleinschreibung interessiert:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Die notwendigen Klassen sind in Flash CS4 und CS5 automatisch bekannt.
// Im Flex/Flash Builder müssen die Klassen importiert werden.
 
// Abonniere Ereignis, um das Drücken der Tasten zu erfahren
stage.addEventListener(KeyboardEvent.KEY_DOWN, application_keyDownHandler);
 
// Ereignismethode, um das Drücken der Tasten zu verarbeiten
function application_keyDownHandler(event:KeyboardEvent):void {
	trace("Taste gedrückt/wiederholt");
	trace("Tastencode: " + event.keyCode);
	trace("Zeichencode: " + event.charCode);
	trace("Zeichen: " + String.fromCharCode(event.charCode));
}

image Barcode-Scanner wie hier der Symbol LS1203 machen sich übrigens meist auch die Tastatur zu nutze. Ein gescannter Barcode erscheint dann – angereichert um ein paar Steuerzeichen – als Tastatureingabe in der Anwendung. Barcode-Scanner sind somit oft nur eine benutzerfreundliche Variante der manuellen Eingabe: Der Zugbegleiter kann Tickets so schneller verarbeiten und der Schlosser muss seine Handschuhe nicht erst ausziehen.