czwartek, 29 maja 2008

Szybkie wektory

Wprowadzenie typoów parametrycznych albo jak kto woli generycznych (z C# 2 czy Java 1.5) miało na celu umożliwienie tworzenia tablic danego typu na poziomie języka programowania. Pewien Japończyk zrobił testy co to daje w kontekście wydajnościowym. Uprzedzam czytelników że ten typ danych ułatwia działania na API do rysowania ale nie przyśpiesza jego działania jeżeli chodzi o wydajność z tradycyjnym sposobem rysowania.
Zmodyfikuję kod do testów ale na początek zamieszczę kod do "okienka" w którym będziemy mogli podziwiać wyniki zamiast tradycyjnego trace(). Kod pochodzi z tej strony.


package com.senocular.utils {
import flash.display.Shape;
import flash.display.Sprite;
import flash.display.Stage;
import flash.display.GradientType;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Matrix;
import flash.text.TextField;
import flash.text.TextFieldType;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
import flash.text.TextFieldAutoSize;

/**
* Creates a pseudo Output panel in a publish
* swf for displaying trace statements.
* For the output panel to capture trace
* statements, you must use Output.trace()
* and add an instance to the stage:
* stage.addChild(new Output());
*
* Note: You may want to place Output in an
* unnamed package to make it easier to
* trace within your classes without having
* to import com.senocular.utils.Output.
*/
public class Output extends Sprite {
private var output_txt:TextField;
private var titleBar:Sprite;
private static var instance:Output;
private static var autoExpand:Boolean = true;
private static var maxLength:int = 1000;

public function Output(outputHeight:uint = 150){
if (instance && instance.parent){
instance.parent.removeChild(this);
}

instance = this;
addChild(newOutputField(outputHeight));
addChild(newTitleBar());

addEventListener(Event.ADDED, added);
addEventListener(Event.REMOVED, removed);
}

// public methods
public static function trace(str:*):void {
if (!instance) return;
instance.output_txt.appendText(str+"\n");
if (instance.output_txt.length > maxLength) {
instance.output_txt.text = instance.output_txt.text.slice(-maxLength);
}
instance.output_txt.scrollV = instance.output_txt.maxScrollV;
if (autoExpand && !instance.output_txt.visible) instance.toggleCollapse();
}
public static function clear():void {
if (!instance) return;
instance.output_txt.text = "";
}

private function newOutputField(outputHeight:uint):TextField {
output_txt = new TextField();
output_txt.type = TextFieldType.INPUT;
output_txt.border = true;
output_txt.borderColor = 0;
output_txt.background = true;
output_txt.backgroundColor = 0xFFFFFF;
output_txt.height = outputHeight;
var format:TextFormat = output_txt.getTextFormat();
format.font = "_typewriter";
output_txt.setTextFormat(format);
output_txt.defaultTextFormat = format;
return output_txt;
}
private function newTitleBar():Sprite {
var barGraphics:Shape = new Shape();
barGraphics.name = "bar";
var colors:Array = new Array(0xE0E0F0, 0xB0C0D0, 0xE0E0F0);
var alphas:Array = new Array(1, 1, 1);
var ratios:Array = new Array(0, 50, 255);
var gradientMatrix:Matrix = new Matrix();
gradientMatrix.createGradientBox(18, 18, Math.PI/2, 0, 0);
barGraphics.graphics.lineStyle(0);
barGraphics.graphics.beginGradientFill(GradientType.LINEAR, colors, alphas, ratios, gradientMatrix);
barGraphics.graphics.drawRect(0, 0, 18, 18);

var barLabel:TextField = new TextField();
barLabel.autoSize = TextFieldAutoSize.LEFT;
barLabel.selectable = false;
barLabel.text = "Output";
var format:TextFormat = barLabel.getTextFormat();
format.font = “_sans”;
barLabel.setTextFormat(format);

titleBar = new Sprite();
titleBar.addChild(barGraphics);
titleBar.addChild(barLabel);
return titleBar;
}

// Event handlers
private function added(evt:Event):void {
stage.addEventListener(Event.RESIZE, fitToStage);
titleBar.addEventListener(MouseEvent.CLICK, toggleCollapse);
fitToStage();
toggleCollapse();
}
private function removed(evt:Event):void {
stage.removeEventListener(Event.RESIZE, fitToStage);
titleBar.removeEventListener(MouseEvent.CLICK, toggleCollapse);
}
private function toggleCollapse(evt:Event = null):void {
if (!instance) return;
output_txt.visible = !output_txt.visible;
fitToStage(evt);
}
private function fitToStage(evt:Event = null):void {
if (!stage) return;
output_txt.width = stage.stageWidth;
output_txt.y = stage.stageHeight - output_txt.height;
titleBar.y = (output_txt.visible) ? output_txt.y - titleBar.height : stage.stageHeight - titleBar.height;
titleBar.getChildByName("bar").width = stage.stageWidth;
}
}
}


Mając taki kod możemy przeprowadzić testy. Test Japończyka był prosty: chciał połączyć dane z tablicy za pomocą metody concat. Aby się upewnić jak szybko za działa łączenie na wartościach trzeba było przyjąć pewne założenia: operujemy na wartościach liczb 16, bo w ten sposób zapisane są kolory. Ponieważ Pixel Bender operuje na wartościach liczb przedstawiających kolory więc naturalną rzeczą będzie obsługa danych na kolorach. Oto kod operujący na typie Array.

package
{
import flash.display.Sprite;
import flash.utils.*;
import com.senocular.utils.Output;

[SWF(width=1024, height=1024, backgroundColor=0xFFFFFF, frameRate=30)]
public class CalcArray extends Sprite
{
private static const SIZE:uint = 1024;
private var startTime:Number;
private var endTime:Number;

public function CalcArray():void
{
stage.addChild(new Output());
var array:Array = [];
for (var h:int = 0; h < SIZE; h++)
{
for (var w:int = 0; w < SIZE; w++)
{
array.push(0xFF000000);
}
}
this.startTime=getTimer();
var i:int;
var l:int;
for (i = 0, l = array.length; i < l; i++)
{
array[i] += 0x00FF0000;
}
var da:Array = array.concat();
this.endTime=getTimer();
Output.trace(this.endTime-this.startTime +' ms');
l = SIZE * SIZE - 1;
Output.trace("Array => 0x" + da[l].toString(16).toUpperCase());
}
}
}


Może łączenie tablic nie jest takie interesujące ale wyraźnie pokazuje na korzyść stosowania danych parametrycznych. Oto kod z wektorami w pliku CalcVector.as :

package
{
import flash.display.Sprite;
import flash.utils.*;
import com.senocular.utils.Output;
[SWF(width=1024, height=1024, backgroundColor=0xFFFFFF, frameRate=30)]
public class CalcVector extends Sprite
{
private static const SIZE:uint = 1024;
private var startTime:Number;
private var endTime:Number;


public function CalcVector():void
{
stage.addChild(new Output());
var vector:Vector.< uint > = new Vector.();
for (var h:int = 0; h < SIZE; h++)
{
for (var w:int = 0; w < SIZE; w++)
{
vector.push(0xFF000000);
}
}
this.startTime=getTimer();
var i:int;
var l:int;
for (i = 0, l = vector.length; i < l; i++)
{
vector[i] += 0x00FF0000;
}
var dv:Vector. = Vector.< uint >(vector.concat());
this.endTime=getTimer();
Output.trace(this.endTime-this.startTime +' ms');
l = SIZE * SIZE - 1;
Output.trace("Vector => 0x" + dv[l].toString(16).toUpperCase());
}
}
}


Na dość wolnym komputerze wynik dla Array wyniósł mi 512 ms, a dla Vector 98 ms.

Skoro mam wolny komputer to wykonywanie Pixel Bender może spowolnić m.in dlatego że nie mam wielordzeniowego procesora. A jak ktoś ma nowszy procesor to nawet pójdzie szybciej. Ale zobaczmy kod Japończyka dla pliku Calc.pbk.

<languageVersion : 1.0;>
kernel ColorFilter
<
namespace : "muta";
vendor : "muta";
version : 1;
description : "This filter Fill color.";
>
{
parameter pixel4 color
<
defaultValue : pixel4(0, 0, 0, 1);
description : "color";
>;
input image4 src;
output pixel4 dst;
void evaluatePixel()
{
pixel4 tmp = sampleNearest(src, outCoord());
tmp.rgba += color;
dst = tmp;
}
}

A kod w pliku ShaderCalc.as jest taki

package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Shader;
import flash.display.Sprite;
import flash.filters.ShaderFilter;
import flash.geom.Point;
import flash.utils.ByteArray;
import flash.utils.*;
import com.senocular.utils.Output;

[SWF(width=1024, height=1024, backgroundColor=0xFFFFFF, frameRate=30)]
public class CalcShader extends Sprite
{
private static const SIZE:uint = 1024;
private var startTime:Number;
private var endTime:Number;

[Embed(source="calc.pbj", mimeType="application/octet-stream")]
private var shaderData:Class;

public function CalcShader():void
{
stage.addChild(new Output());

var vector:Vector. = new Vector.();

for (var h:int = 0; h < int =" 0;" bitmapdata =" new" bitmapdata =" new" point =" new" shader =" new" shaderfilter =" new" starttime="getTimer();" value =" [1,"> = dst.getVector(dst.rect);

this.endTime=getTimer();
Output.trace(this.endTime-this.startTime +' ms');

l = SIZE * SIZE - 1;

Output.trace("Shader + Vector => 0x" + ds[l].toString(16).toUpperCase());
addChild(new Bitmap(dst));
}
}
}

Dla mnie wynik wyniósł w działaniu powyższego kodu 160 ms. co i tak na słabym komuterze daje całkiem niezły wynik.

poniedziałek, 26 maja 2008

As3 Wrapper

W poprzednim poście wspomniałem o pewnej bibliotece która miała pozwolić programistom JavaScriptu na bezproblemowe przejście do programowania w JavaScript 2.0 ( i zwiększyć wydajność obsługi skryptów). A dzisiaj odkryłem kolejną możliwość pisania skryptów JavaScript zamiast kodu w ActionScript 3 za pomocą biblioteki AS3Wrapper. Jest jeden duży plus tego rozwiązania: pozwala programistom generować po stronie serwera skrypty JavaScript, które miałyby wpływ na dynamiczny sposób wyświetlania zawartości przez Flash Playera.
Wadą tej biblioteki jest brak kodu źródłowego i ograniczenie na razie tego do modułów Papervision, Google Maps Flash API, jakiejś biblioteki do obsługi czcionek. Przykłady można pobrać stąd.

Oto kod wykorzystujący Google Maps Flash API


var player;
var map;

function pageLoad(){
player = new AS3Wrapper.Player("flashcontent",200,200,false);
player.onload = function(){
player.loadModule('GoogleMaps',onload2);
}
function onload2(e)
{
with(player)
with(com.google.maps){
map = new Map();
map.setKey('=== wpisz tutaj swój klucz Google API ====');
map.addEventListener(MapEvent.MAP_READY, onMapReady);
root.addChild(map);
}
}
}

function onMapReady(event)
{
with(player)
with(com.google.maps){
map.setCenter(new LatLng(40.736072,-73.992062), 14, MapType.NORMAL_MAP_TYPE);
}
}




Jak widać przypomina to czasy pisania w starym ActionScript 1.0

niedziela, 25 maja 2008

JS 2.0 - prawie zrealizowana wizja....

W jednym poście pisałem o JavaScript 2.0 oraz o wydajności jakie to niesie ze sobą dla aplikacji Web 2.0. Moja wizja w której programiści Web 2.0 zaczynają pisać kod pod JavaScript 2.0 miała słaby punkt. Co zrobić ze starymi przeglądarkami? Otóż jest już rozwiązanie: ScreamingDonkey
Cała filozofia tego rozwiązania polega na tym, żeby napisać kod w JavaScript 2.0 wysłać go do FlashPlayera, FlashPlayer skompiluje kod JS 2.0 do postaci wykonywalnej przez AVM2 (Tamarin) dokona obliczeń i wyniki wyśle jako JSObject do przeglądarki. Nie byłem do tego przekonany z tego powodu, że to jest zbyt innowacyjne, aby programiści JavaScriptu zaczęli poważnie to potraktować.
Z innej strony raczej wierzę w to, że przeglądarka oparta na Adobe AIR "wygryzie" pozostałe przeglądarki internetowe. Ale trzeba uszanować tych którzy z jakiś tam powodów nie będą mogli korzystać z Adobe AIR. W tym momencie włączenie ScreamingDonkey jako silnika Java Script 2.0 do przeglądarki opartej na Adobe AIR rozwiązuje problem sytuację w której na stronie internetowej mamy kod JavaScript 2.0. Oznacza to dla użytkowników internetowych że ten kod zostanie wykonany w Firefoxie 2 i 3 (nie muszą czekać na wydanie Firefoxa 4), w Internet Exploratorach będzie mógł wybierać pomiędzy silnikiem ScreamingMonkey a ScreamingDonkey. Zagadką pozostaje Opera i jak Safari 3 - myślę, że w przypadku tych przeglądarek najszybciej wprowadzą natywna obsługę JavaScriptu 2.0
To co mnie naprawdę zdumiało to jest szybkość Flash Playera w testach wydajnościowych. W pewnym benchmarku otrzymałem wynik 13237.40 ms pod Firefoxem 2, a ten sam test w ScreamDonkey pod Firefoxem 2 z Flash Playerem 10 to 2160.00 ms. W praktyce otwiera to drogę przed obliczeniami graficznymi w przeglądarkach. Jednym słowem przeglądarka będzie mogła dość skutecznie uzupełniać Flash Playera. John Resig opublikował 8 maja bibliotekę JavaScript Processing.js do efektów graficznych na bazie silnika Processing. A Metal Hurlant 19 maja już opublikował wersję działającą z ScreamDonkey pod Firefoxem 2. A na horyzoncie mamy ActionScript 4, który będzie opracowywany na bazie EcmaScript4. Pracownik Adobe już napisał wprowadzenie do tego co będzie w kolejnej wersji EcmaScript 4 Hurlant już dodał obsługę bajtkodu es4 w bibliotece AS 3 Eval o.3 do FlashPlayera.

sobota, 24 maja 2008

Blending Mode

Ponieważ japońscy programiści zajmują się ustalaniem jak można przerzucić obliczenia na shadery i wykorzystać ShaderJob do symulowania wielowątkowej pracy. To chciałbym pokazać kolejne możliwości Pixel Bender. Taka możliwością jest stosowanie trybów mieszania.

W Pixel Bender Toolkit jest kod shadera crossfade.pbk

<languageVersion : 1.0;>
kernel Crossfade
< namespace : "AIF";
vendor : "Adobe Systems";
version : 2;
description : "Crossfade between two images"; >
{

parameter float intensity;
input image4 frontImage;
input image4 backImage;
output pixel4 dst;
void evaluatePixel()
{
float4 frontPixel = sampleNearest(frontImage, outCoord());
float4 backPixel = sampleNearest(backImage, outCoord());
dst = mix(frontPixel, backPixel, intensity);
}
}
Funkcję mix można przedstawić jako odpowiednik takiego działania:
dst = (1.0 - intensity) * frontPixel + intensity * backPixel

A jaki napisać kod ActionScript 3 do obsługi trybów mieszania z bajtkodu crossfade.pbj?
Na przykład taki kod ActionScript 3 jak ten w pliku Crossfade.as

package{
import flash.display.MovieClip;
import flash.display.Shader;
import flash.events.Event;
import mx.core.BitmapAsset;
import flash.display.StageScaleMode;
[SWF(width="500", height="500")]
public class Crossfade extends MovieClip{

[Embed(source = 'front.jpg')]
[Bindable]
private var _frontImage:Class;

[Embed(source = 'back.jpg')]
[Bindable]
private var _backImage:Class;

[Embed(source="crossfade.pbj", mimeType="application/octet-stream")]
private var CrossShader:Class;

private var _shader:Shader;
private var _frontAsset:BitmapAsset;
private var _backAsset:BitmapAsset;


public function Crossfade()
{
super();
_frontAsset = new _frontImage() as BitmapAsset;
_backAsset = new _backImage() as BitmapAsset;
stage.scaleMode = StageScaleMode.NO_SCALE;

_shader = new Shader(new CrossShader());
_shader.data.frontImage.input = _frontAsset.bitmapData;
_shader.data.backImage.input = _backAsset.bitmapData;
_shader.data.intensity.value = [0.72];
addEventListener(Event.ENTER_FRAME, drawShader);
}


private function drawShader(e:Event):void
{
graphics.clear();
graphics.beginShaderFill(_shader);
graphics.drawRect(0, 0, 500, 500);
graphics.endFill();
}
}
}


Jak widać możliwości są całkiem spore.

piątek, 23 maja 2008

Adobe Flex 4 i Pixel Bender

Aby programować pod Flash Player 10 trzeba ściągnąć najnowszą wersję Flex 4 SDK
Rozpakowujemy to archiwum do katalogu D:\Flex4\. Następnie dodajemy do zmiennej środowiskowej PATH ścieżkę D:\Flex4\bin\ gdzie mamy kompilator mxmlc.exe.
Trzeba jeszcze sprawdzić w pliku D:\flex4\frameworks\flex-config.xml czy mamy dobrze ustawione parametry do kompilacji pod Flash Player 10. Trzeba tylko do kompilacji używać takiego zapisu:

mxmlc.exe --target-player=10 PixelBender.as


Są 2 sposoby pisania kodu pod dla bajtkodu Pixel Bender. Pierwszy z nich polega na pobraniu czy wysłaniu pliku *.pbj do pliku SWF, a drugi polega na osadzeniu pliku *.pbj w pliku SWF

Zaczniemy od drugiego sposobu. Załadujemy grafikę i jak bajtkod shadera w plik SWF.

Oto kod shadera sepia3.pbk

<languageVersion: 1.0;>
kernel sepia
< namespace : "AIF";
vendor : "Adobe Systems";
version : 2;
description : "a variable sepia filter"; >
{

input image4 src;
output float4 dst;
void
evaluatePixel()
{
float4 rgbaColor;
float4 yiqaColor;
float4x4 YIQMatrix = float4x4(
0.299, 0.596, 0.212, 0.000,
0.587, -0.275, -0.523, 0.000,
0.114, -0.321, 0.311, 0.000,
0.000, 0.000, 0.000, 1.000
);
float4x4 inverseYIQ = float4x4(
1.0, 1.0, 1.0, 0.0,
0.956, -0.272, -1.10, 0.0,
0.621, -0.647, 1.70, 0.0,
0.0, 0.0, 0.0, 1.0
);

rgbaColor = sampleNearest(src, outCoord());
yiqaColor = YIQMatrix * rgbaColor;
yiqaColor.y = 0.2;
yiqaColor.z = 0.0;
dst = inverseYIQ * yiqaColor;
}
}


Po kompilacji mamy w postaci bajtkodu plik sepia2.pbj. Pozostaje tylko napisać kod ActionScriptu 3 w pliku Osadzony.as.


package {
import flash.display.Bitmap;
import flash.display.Shader;
import flash.display.Sprite;
import flash.events.Event;
import flash.filters.ShaderFilter;

[SWF(width="550",height="400",frameRate="24")]

public class Osadzony extends Sprite
{

[Embed(source="rose.jpg")]
private var Obraz:Class;

[Embed(source="sepia2.pbj", mimeType="application/octet-stream")]
private var Sepia:Class;

private var img:Bitmap;
private var shader:Shader;
private var filter:ShaderFilter;

public function Osadzony(){

img = new Obraz() as Bitmap;
img.x = stage.stageWidth/2 - img.width/2;
img.y = stage.stageHeight/2 - img.height/2;
addChild(img);

shader = new Shader(new Sepia());
filter = new ShaderFilter(shader);
img.filters = [filter];

}
}
}

Teraz napiszemy kod w pliku LoadedShader.as do sytuacji w której załadujemy lokalnie plik bajtkodu z Pixel Binder.


package
{
import flash.display.*;
import flash.events.*;
import flash.net.*;
import flash.filters.*;
import flash.utils.*;
[SWF(width="550",height="400",frameRate="24")]
public class LoadedShader extends Sprite
{
[Embed(source="rose.jpg")]
private var Obraz:Class;
private var fr:FileReference;
private var shader:Shader;
private var shaderFilter:ShaderFilter;
private var loader:Loader;
private var img:Bitmap;

public function LoadedShader():void
{
fr = new FileReference();
fr.addEventListener(Event.SELECT, onSelect);
fr.addEventListener(Event.COMPLETE, onComplete);
var spr:Sprite = new Sprite();
addChild(spr);
img = new Obraz() as Bitmap;
img.x = stage.stageWidth/2 - img.width/2;
img.y = stage.stageHeight/2 - img.height/2;
spr.addChild(img);
spr.addEventListener(MouseEvent.CLICK, loaderClick);
}

private function loaderClick(e:Event):void
{
fr.browse();
}

private function onSelect(e:Event):void
{
fr.load();
}

private function onComplete(e:Event):void
{
shader = new Shader(fr.data);
shaderFilter = new ShaderFilter();
shaderFilter.shader = shader;
img.filters = [shaderFilter];
}
}
}
Jest jeszcze inny "sposób", który pokazuje jak plik shadera i grafikę załadować z zewnątrz (do testowania na serwerze). W pliku OuterPixelBender.as mamy taki kod ActionScript


package
{
import flash.display.*;
import flash.events.*;
import flash.net.*;
import flash.filters.*;
import flash.utils.*;

public class OuterPixelBender extends Sprite
{

private var shader:Shader;
private var shaderFilter:ShaderFilter;
private var loader:Loader;

public function OuterPixelBender():void
{
var shaderLoader:URLLoader = new URLLoader();
shaderLoader.dataFormat = URLLoaderDataFormat.BINARY;
shaderLoader.addEventListener(Event.COMPLETE, shaderLoaded);
shaderLoader.load(new URLRequest("sepia2.pbj"));
loader = new Loader();
loader.load(new URLRequest("rose.jpg"));
addChild(loader);

}

private function shaderLoaded(e:Event):void
{
var shaderLoader:URLLoader = e.target as URLLoader;
try
{
shader = new Shader();
shader.byteCode = shaderLoader.data;
shaderFilter = new ShaderFilter();
shaderFilter.shader = shader;
loader.filters = [shaderFilter];

}catch (e:Error)
{
throw(e);
}
}
}
}


A tak na marginesie to zauważyłem, że mało kto pisze że, można już używać Adobe Flex 4 SDK i stąd taki przewrotny tytuł posta.

czwartek, 22 maja 2008

Programowanie Shaderów w Pixel Bender

Jak wspomniałem za programowanie filtrów i shaderów (blendingu) w Flash Player 10 odpowiada technologia Bender Pixel. Najpierw instalujemy Pixel Bender Toolkit i instalacja przebiega po polsku. Po zainstalowaniu Pixel Bender Toolikt uruchamiamy to cudo - może sie pojawić komunikat po angielsku, że nie mamy karty GPU. Z menu File -> Load Images wybieramy jakiś obrazek. Następnie wybieramy skrypt z menu File -> Open Pixel Bender Kernel Filter i wskazujemy sciezkę C:\Program Files\Adobe\Adobe Utilities\Pixel Bender Toolkit\pixel bender files i wybieramy plik sepia.pbk. Uruchamiamy to z menu Build -> Run
Ustawiamy w pasku scrollera intensity na 0.2.Jeśli podoba się to nam to tworzymy plik bajtkod dla Flash Player 10. Z memu wybieramy File -> Export Pixel Bender Byte Code for Flash i zapisujemy jako sepia.pbj. Następnie wchodzimy na tą stronę. Klikamy w przycisk Add
Pojawi sie okno do wybrania pliku wybieramy (tam gdzie zapisaliśmy) plik sepia.pbj
Ale nie sobaczyliśmy czego chcieliśmy czyli efektu sepii. Wracamy do Pixel Bender Toolkit. W kodzie pliku sepia.pbk szukamy linijki 86 która zawiera kod:
yiqaColor.y = intensity;
zmieniamy to na
yiqaColor.y = 0.2;
Zapisujemy to wybierając z menu File -> Save Pixel Blender Kernel Filter As.. jako sepia2.pbk. A następnie wybieramy z memu File -> Export Pixel Bender Byte Code for Flash i zapisujemy jako sepia2.pbj. Następnie wchodzimy i odświeżamy tą stronę. Klikamy w przycisk Add. Pojawi sie okno do wybrania pliku i wybieramy (tam gdzie zapisaliśmy) plik sepia2.pbj. I zobaczyliśmy to co chcieliśmy.

Na początek programowania weźmiemy sobie kod który nic nie robi a jest podstawą do pracy nad kolejnymi skryptami

<languageVersion : 1.0;>
kernel NowyFiltr
< namespace : "info.malaj";
vendor : "Michal Malaj";
version : 1;
description : "Pierwszy kod";
>
{
input image4 src;
output pixel4 dst;
void
evaluatePixel()
{
dst = sampleNearest(src,outCoord());
}
}


Nazwa kernela nie może zawierać polskich znaków, i tak lepiej poza komentarzami i metatagami nie używać w plikach języka polskiego. Kopiujemy powyższy kod i zapisujemy go. Otóż powyższy kod działa w ten sposób, że pobiera dane z obrazka. Słowo kluczowe input oznacza że dane są wyjściowe typu image4 i są zdefiniowanej w zmiennej o nazwie src. Następne słowo kluczowe output oznacza że dane będą wynikiem końcowym jako typu pixel4 (oznacza to że informacje o kolorze jednego piksela są przechowywane w 4 wartościach RGBA) i są zdefiniowanej w zmiennej dst. Następnie mamy zwykła procedurę evaluatePixel(), która dokonuje obliczeń na tych danych. Tym razem mamy jedną linijkę kodu przypisujemy do zmiennej dst wynik działania funkcji sampleNearest(). Ta funkcja pobiera dane z obrazka i o jego współrzędnych i przekształca to na piksele, na których dalej będzie można dokonywać operacji. W tym przypadku można powiedzeć że zwraca informacje o pikselach w tym obrazku i nic więcej.
Teraz dokonamy operacji przyciemniania obrazu oznacza to, że każda wartość z RGBA zostanie pomniejszona o połowę. Matematyczne to polega na pomnożeniu przez wartość 0.5.

Oto kod przyciemnianie.pbk
<languageVersion : 1.0;>
kernel Przyciemnianie
< namespace : "malaj.info";
vendor : "Michał Małaj";
version : 1;
description : "efekt przyciemniania";
>
{
input image4 src;
output pixel4 dst;
void
evaluatePixel()
{
dst = 0.5 * sampleNearest(src,outCoord());
}
}

Wadą powyższego rozwiązania jest to, że kanał alfa stawał sie bardziej przezroczysty i widać było przenikanie tła. Kolejny przykład pokaże jak sobie z tym poradzić

Kod przyciemnianiebezalfy.pbk
<languageVersion : 1.0;>
kernel Przyciemnianie
< namespace : "info.malaj";
vendor : "Michał Małaj";
version : 1;
description : "Przyciemnianie bez alfy";
>
{
input image4 src;
output pixel4 dst;
void
evaluatePixel()
{
float4 inputColor = sampleNearest(src,outCoord());
dst.rgb = 0.5 * inputColor.rgb;
dst.a = inputColor.a;
}
}

Podobnie można zrobić filtr ekspozycji. Potocznie mówi się że to jest rozjaśnianie.

<languageVersion : 1.0;>
kernel Rozjasnianie
< namespace : "info.malaj";
vendor : "Michal Malaj";
version : 1;
description : "Filtr ekspozycji";
>
{
input image4 src;
output pixel4 dst;
void
evaluatePixel()
{
float4 inputColor = sampleNearest(src, outCoord());
dst.rgb = pow(inputColor.rgb, float3(0.5));
dst.a = inputColor.a;
}
}

Najlepszą rzeczą jaką jest w tej technologii to modyfikacja parametrów podczas działania. Czyli trzeba dodać parametry, które mogą być modyfikowane. Oto kod parametry.pbk, który pozwoli na modyfikację parametru ekspozycji naświetlania.

<languageVersion : 1.0;>
kernel Rozjasnianie
< namespace : "info.malaj";
vendor : "Michal Malaj";
version : 1;
description : "Filtr ekspozycji";
>
{
input image4 src;
output pixel4 dst;
parameter float exposure
<
minValue:float(-0.5);
maxValue:float(0.5);
defaultValue:float(0.0);
>;
void
evaluatePixel()
{
float4 inputColor = sampleNearest(src, outCoord());
dst.rgb = pow(inputColor.rgb, float3(1.0 - exposure));
dst.a = inputColor.a;
}
}

Teraz zrobimy efekt czarno-białej fotografii

<languageVersion : 1.0;>
kernel BlackWhite
< namespace : "info.malaj";
vendor : "Michał Małaj";
version : 1;
description : "efekt czarno-białej fotografii";
>
{
input image4 src;
output pixel4 dst;
void
evaluatePixel()
{
dst = sampleNearest(src,outCoord()).rrra;
}
}

Jak widać oznacza to, że kompilator bierze tylko z dane z pikseli kanału koloru czerwonego i przypisuje jej zwartość do kanału koloru niebieskiego i zielonego. To dobrze działa jak na zdjęciu mamy dużo czerwonego koloru. Więc trzeba dać użytkownikowi możliwość wpływania na takie działanie. W tej sytuacji pokażę kod w którym w wyniku zmiany parametru obraz przekształca sie w czarno-białą fotografię.

<languageVersion : 1.0;>
kernel ParamBlackWhite
< namespace : "info.malaj";
vendor : "Michał Małaj";
version : 1;
description : "efekty przejścia w czarno-białą fotografię";
>
{
input image4 src;
output pixel4 dst;
parameter float crossfade;
void
evaluatePixel()
{
dst = sampleNearest(src,outCoord());
float3 bw = dst.rrr;
dst.rgb = ((1.0 - crossfade) * dst.rgb) + (crossfade * bw);
}
}
To widać jak mało doskonałe jest przetwarzanie kolorów. W tym momencie przychodzi znajomość luminacji. W skrócie to polega na obliczeniu natężenia światła. Dla naszych potrzeb wystarczy już gotowy wzór dla wartości RGB Y = 0.2126 R + 0.7152 G + 0.0722 B

<languageVersion : 1.0;>
kernel LunaBlackWhite
< namespace : "info.malaj";
vendor : "Michał Małaj";
version : 1;
description : "efekty lumy przejścia w czarno-białą fotografię";
>
{
input image4 src;
output pixel4 dst;
parameter float crossfade;
void
evaluatePixel()
{
dst = sampleNearest(src,outCoord());
float luminance = dst.r * 0.212 + dst.g * 0.715 + dst.b * 0.07;
dst.rgb = ((1.0 - crossfade) * dst.rgb) + (crossfade * float3(luminance));
}
}
Luminację można zrobić przy pomocy iloczynu skalarnego i wykorzystać funkcję mix(), odpowiadającą za liniową interpolacją kanałów RGB z tą luminacją

<languageVersion : 1.0;>
kernel LunaDotBlackWhite
< namespace : "info.malaj";
vendor : "Michał Małaj";
version : 1;
description : "efekty lumy z iloczynem skalarnym przejścia w czarno-białą fotografię";
>
{
input image4 src;
output pixel4 dst;
parameter float crossfade;
const float3 lumMult = float3(0.212, 0.715, 0.07);
void
evaluatePixel()
{
dst = sampleNearest(src,outCoord());
float luminance = dot(dst.rgb, lumMult);
dst.rgb = mix(dst.rgb, float3(luminance), crossfade);
}
}

Warto przyjrzeć się kolejnemu kodu odpowiedzialnego za wygenerowanie sepii

<languageVersion : 1.0;>
kernel Sepia
< namespace : "info.malaj";
vendor : "Michał Małaj";
version : 1;
description : "efekty przejścia w fotografię sepii";
>
{
input image4 src;
output pixel4 dst;
parameter float crossfade
<
minValue:float(0.0);
maxValue:float(1.5);
defaultValue:float(1.0);
>;
const float3 lumMult = float3(0.212, 0.791, 0.07);
const float3x3 sepiaMatrix = float3x3(0.400, 0.769, 0.189,0.349, 0.686, 0.168, 0.272, 0.534, 0.131);

void
evaluatePixel()
{
dst = sampleNearest(src,outCoord());
float3 sepia = dst.rgb * sepiaMatrix;
dst.rgb = mix(dst.rgb, sepia, crossfade);
}
}

Zawsze można wygenerować bajtkod z tych efektów dla Flash Playera 10. Jak widać kod efektów jest bardzo zwięzły. Warto pokazać jak za pomocą ActionScript 3 można wykorzystać ten bajtkod.

czwartek, 15 maja 2008

Flash Player 10 Beta

Adobe wypuściło wersję Flash Playera 10 Beta. Ale mnie zainteresowało jak mogę już brać się za kodowanie do Flash Playera 10 Beta. Wystarczyłoby ściągnąć nighty builda Flex 3 SDK. Obejrzałem dema, ale miałem problem ze ściągnięciem kodów źródłowych, ale zamieszczę linki do tych źródeł: IK Sample, Pixel_Bender Sample, NewTextEngine, Native3D.

Z powodu braku dokumentacji (przynajmniej ja jeszcze ją nie znalazłem - będzie wraz z dokumentacja do Flex 4), to warto się skoncentrować na filmach Justina Everett-Church, który pokazuje o obsłudze 3D, o Pixel Bender, nowym silniku do wyświetlania tekstu wykorzystującym technologię Saffron 3.1, o nowych możliwościach obsługi GPU i co za tym idzie znaczny wzrost wydajności. Jedynym materiałem w dzisiejszym dniu o nowym API FlashPlayera jest ten kurs

Najbardziej intrygującą rzeczą jest Flie reference runtime access co pozwala użytkownikowi załądować np plik graficzny, prosto z komputera i obsługiwać go z poziomu FlashPlayera a następnie zapisać efekt na dysk twardy jako strumień danych (ByteArray oczywiście).

Efektem intensywnego lobbyingu developerów w akcji Adobe, Make Some Noise było dodanie możliwości generowania dźwięku o czym napisał inżynier Tinic Uro tworzący FlashPlayera.

Dodano nową nowość do ActionScriptu 3 - typ danych Vector który jest parametrycznym typem danych. Może będzie trzeba już uczyć się ActionScriptu 4.

Utworzono nowy komponent pod kodową nazwą "vellum_flash" do wielokolumnowego tekstu, z obsługą tabel, "pływających ramek", obsługi innych systemów pisma. Ten komponent będzie wymagał nauki nowego języka znacznikowego XFL (który ma zastąpić format FLA).

Kolejna rzeczą zadziwiającą jest wykorzystanie P2P dzięki dodaniu obsługi Media Flow Protocol (był on kiedyś dostępny). Następnie dzięki dodaniu nowego kodeka Speex Audio Codex odpowiedzialnego za obsługę kodowanie dźwięku z mikrofonu może ktoś zrobi konkurenta dla Skypa i telefonii internetowej.

Mam też z pliku playerglobal.swc z Flex 3 SDK informacje o nowym API. Adobe już wypuściło dokumentację API w tym pliku Są nowe przestrzenie nazw. To najlepiej pokazuje nowe możliwości. Flash Playera. Do tej pory Japończycy opublikowali API do fp10 Przestrzeń nazw odpowiadającą za obróbkę dźwięku to flash.sampler.*

Nowe klasy z flash.display.* to
flash.display.IGraphicsPath
flash.display.IGraphicsData
flash.display.GraphicsPath
flash.display.ShaderPrecision
flash.display.TriangleCulling
flash.display.Shader
flash.display.ShaderData
flash.display.ShaderParameterType
flash.display.ShaderInput
flash.display.GraphicsShaderFill

Klasy związane z nowym silnikiem do obsługi tekstu są w pakiecie flash.text.engine.*
flash.text.engine.ContentElement
flash.text.engine.TextJustifier
flash.text.engine.SpaceJustifier
flash.text.engine.TypographicCase
flash.text.engine.TabAlignment
flash.text.engine.ElementFormat
flash.text.engine.FontMetrics
flash.text.engine.GraphicElement
flash.text.engine.TextLine
flash.text.engine.TextRotation
flash.text.engine.TextBlock
flash.text.engine.LigatureLevel
flash.text.engine.TextBaseline
flash.text.engine.FontPosture
flash.text.engine.TextLineValidity
flash.text.engine.TextLine
flash.text.engine.GroupElement

Klasy z pakietu flash.ui.* - odpowiadają za interakcję
flash.ui.ContextMenuItem
flash.ui.ContextMenuBuiltInItems
flash.ui.KeyLocation
flash.ui.MouseCursor
flash.ui.Keyboard

Klasy z pakietu flash.geom.* zostały wzbogacone o obsługe przekształceń
flash.geom.PerspectiveProjection
flash.geom.Vector3D
flash.geom.Utils3D

Dostały też dodane całkiem nowe klasy powiązane z innymi nowymi możliwościami Flash Playera 10

flash.events.ShaderEvent
flash.events.ContextMenuEvent
flash.desktop.ClipboardFormats
flash.filters.ShaderFilter
flash.media.SoundCodec
flash.system.JPEGLoaderContext
flash.net.DynamicPropertyOutput
flash.net.IDynamicPropertyWriter

środa, 14 maja 2008

Modelowanie danych w Google App Engine

Zauważyliśmy że trzeba większa uwagę poświęcić na modelowaniu danych. Ale najlepszym mechanizmem do nauki jest interaktywna konsola
http://localhost:8080/_ah/admin/interactive

Robimy analizę przykładu 1:1. Wystarczy poniższy kod skopiować w interaktywną konsole i uruchomić.


from django.db import models
from google.appengine.ext import db

class Car(db.Model):
brand = db.StringProperty(required=True)
wheels = db.ListProperty(db.Key)

class Human(db.Model):
name = db.StringProperty(required=True)
drives = db.ReferenceProperty(reference_class=Car)
spouse = db.SelfReferenceProperty()
owns = db.ListProperty(db.Key)

class Wheel(db.Model):
isBroken = db.BooleanProperty(default=False)
position = db.StringProperty(choices=set(["left_front",
"left_back",
"right_front",
"right_back"]))

# tworzymy obiekt Jacka
jack = Human(name="Jack")
# tworzymy obiekt mercedesa
mercedes = Car(brand="Mercedes")
# przypisujemy obiekt mercedesa jako pojazd Jacka
jack.drives = mercedes.put()
#wstawiamy dane do obiektu Jacka
jack.put()
# wyświetlamy dane
print >> sys.stdout, "Jack ma samochod marki "+jack.drives.brand
# Jacek ma samochód marki Mercedes

Trzeba być ostrożnym bo modelowanie klasyczne jeden do jednego wcale nie jest takie automatyczne obsługiwane przez ORM

from django.db import models
from google.appengine.ext import db

class Car(db.Model):
brand = db.StringProperty(required=True)
wheels = db.ListProperty(db.Key)

class Human(db.Model):
name = db.StringProperty(required=True)
drives = db.ReferenceProperty(reference_class=Car)
spouse = db.SelfReferenceProperty()
owns = db.ListProperty(db.Key)

class Wheel(db.Model):
isBroken = db.BooleanProperty(default=False)
position = db.StringProperty(choices=set(["left_front",
"left_back",
"right_front",
"right_back"]))

jack = Human(name="Jack")
mike = Human(name="Mike")
mercedes = Car(brand="Mercedes")
mercedesid = mercedes.put()

jack.drives = mercedesid
jack.put()

mike.drives = mercedesid
mike.put()

print >> sys.stdout, "Mike ma samochod marki "+mike.drives.brand
#Mike ma samochod marki Mercedes

Ważne jest rozróżnienie odesłania do własnej klasy w kodzie: czyli model odwołuje sie do własnego modelu np: człowiek odwołuje sie do innego człowieka albo albo wynika z oczywistości, że mąż ma żonę co w praktyce oznacza że model model ma odniesienie do siebie samego.

from django.db import models
from google.appengine.ext import db


class Car(db.Model):
brand = db.StringProperty(required=True)
wheels = db.ListProperty(db.Key)

class Human(db.Model):
name = db.StringProperty(required=True)
drives = db.ReferenceProperty(reference_class=Car)
spouse = db.SelfReferenceProperty()
owns = db.ListProperty(db.Key)

class Wheel(db.Model):
isBroken = db.BooleanProperty(default=False)
position = db.StringProperty(choices=set(["left_front",
"left_back",
"right_front",
"right_back"]))
# one-to-one self
bob = Human(name="Bob")
jane = Human(name="Jane")

bob.spouse = jane.put()
bob.put()
b_spouse = Human.get(bob.spouse.key())
print >> sys.stdout, "Bob's spouse is "+b_spouse.name
# Bob's spouse is Jane


Relacja wzajemności. Oto przykład

from django.db import models
from google.appengine.ext import db


class Car(db.Model):
brand = db.StringProperty(required=True)
wheels = db.ListProperty(db.Key)

class Human(db.Model):
name = db.StringProperty(required=True)
drives = db.ReferenceProperty(reference_class=Car)
spouse = db.SelfReferenceProperty()
owns = db.ListProperty(db.Key)

class Wheel(db.Model):
isBroken = db.BooleanProperty(default=False)
position = db.StringProperty(choices=set(["left_front",
"left_back",
"right_front",
"right_back"]))

bob = Human(name="Bob")
jane = Human(name="Jane")

bob.spouse = jane.put()
jane.spouse = bob.put()
jane.put()

j_spouse = Human.get(jane.spouse.key())
print >> sys.stdout, "Jane's spouse is "+j_spouse.name
b_spouse = Human.get(bob.spouse.key())
print >> sys.stdout, "Bob's spouse is "+b_spouse.name

# Jane's spouse is Bob
# Bob's spouse is Jane


Problem w tym, że nikt nie sprawdza poprawności tych relacji.

Relacja jeden do wielu. W programowaniu oznacza, że dany model ma przodka bądź rodzica. Popatrzmy na poniższy kod:

from django.db import models
from google.appengine.ext import db

class Car(db.Model):
brand = db.StringProperty(required=True)
wheels = db.ListProperty(db.Key)

class Human(db.Model):
name = db.StringProperty(required=True)
drives = db.ReferenceProperty(reference_class=Car)
spouse = db.SelfReferenceProperty()
owns = db.ListProperty(db.Key)

class Wheel(db.Model):
isBroken = db.BooleanProperty(default=False)
position = db.StringProperty(choices=set(["left_front",
"left_back",
"right_front",
"right_back"]))

# one-to-many using parent
bmw = Car(brand="BMW")
bmw.put()

lf = Wheel(parent=bmw,position="left_front")
lf.put()

lb = Wheel(parent=bmw,position="left_back")
lb.put()

rf = Wheel(parent=bmw,position="right_front")
rf.put()

# uh, snap, the 4th wheel is broken!
rb = Wheel(parent=bmw,position="right_back",isBroken=True)
rb.put()

# from car to wheels
bmwWheels = Wheel.all().ancestor(bmw)
print >> sys.stdout, "The BMW has the wheels: "
for wheel in bmwWheels:
print >> sys.stdout, "- "+wheel.position

# The BMW has the wheels:
# - left_front
# - right_back
# - right_front
# - left_back

# from wheel to car
brokenWheels = Wheel.gql("WHERE isBroken = :broken",
broken=True)
print >> sys.stdout, "The following cars are broken: "
for wheel in brokenWheels:
print >> sys.stdout, "- "+wheel.parent().brand

# The following cars are broken:


Co jest ciekawego w tym kodzie? Użycie GQL jako zapytania w modelu
w razie czego proponuję poczytać o tym
http://code.google.com/appengine/docs/datastore/entitiesandmodels.html
Teraz weźmiemy kolejny przykład

from django.db import models
from google.appengine.ext import db

class Car(db.Model):
brand = db.StringProperty(required=True)
wheels = db.ListProperty(db.Key)

class Human(db.Model):
name = db.StringProperty(required=True)
drives = db.ReferenceProperty(reference_class=Car)
spouse = db.SelfReferenceProperty()
owns = db.ListProperty(db.Key)

class Wheel(db.Model):
isBroken = db.BooleanProperty(default=False)
position = db.StringProperty(choices=set(["left_front",
"left_back",
"right_front",
"right_back"]))

class OwnedCar(db.Model):
brand = db.StringProperty(required=True)
owner = db.ReferenceProperty(Human, required=True)


paul = Human(name = "Paul")
paul.put()

pauls_bmw = OwnedCar(brand = "BMW", owner = paul)
pauls_bmw.put()

pauls_mercedes = OwnedCar(brand="Mercedes", owner=paul)
pauls_mercedes.put()

pauls_cars = paul.ownedcar_set

print >> sys.stdout, "Paul's cars: "
for car in pauls_cars:
print >> sys.stdout, "- "+car.brand


Relacje jeden do wielu można zrealizować przy pomocy listy

from django.db import models
from google.appengine.ext import db

class Car(db.Model):
brand = db.StringProperty(required=True)
wheels = db.ListProperty(db.Key)

class Human(db.Model):
name = db.StringProperty(required=True)
drives = db.ReferenceProperty(reference_class=Car)
spouse = db.SelfReferenceProperty()
owns = db.ListProperty(db.Key)

class Wheel(db.Model):
isBroken = db.BooleanProperty(default=False)
position = db.StringProperty(choices=set(["left_front",
"left_back",
"right_front",
"right_back"]))

class OwnedCar(db.Model):
brand = db.StringProperty(required=True)
owner = db.ReferenceProperty(Human, required=True)


# one-to-many using list
dodge = Car(brand="Dodge")
w1 = Wheel(position="left_front")
w2 = Wheel(position="left_back")
w3 = Wheel(position="right_front")
w4 = Wheel(position="right_back")
dodge.wheels = [w1.put(),w2.put(),w3.put(),w4.put()]
dodge.put()

dodgeWheels = Wheel.get(dodge.wheels)
print >> sys.stdout, "The Dodge has the wheels: "
for wheel in dodgeWheels:
print >> sys.stdout, "-"+wheel.position


Możemy dalej analizować przykłady relacji wielu do wielu

from django.db import models
from google.appengine.ext import db

class Car(db.Model):
brand = db.StringProperty(required=True)
wheels = db.ListProperty(db.Key)

class Human(db.Model):
name = db.StringProperty(required=True)
drives = db.ReferenceProperty(reference_class=Car)
spouse = db.SelfReferenceProperty()
owns = db.ListProperty(db.Key)

class Wheel(db.Model):
isBroken = db.BooleanProperty(default=False)
position = db.StringProperty(choices=set(["left_front",
"left_back",
"right_front",
"right_back"]))

class OwnedCar(db.Model):
brand = db.StringProperty(required=True)
owner = db.ReferenceProperty(Human, required=True)

# many-to-many using list+db.Key
jack = Human(name="Jack")
bob = Human(name="Bob")

vw = Car(brand="VW")
chevy = Car(brand="Chevy")

carpool = [vw.put(),chevy.put()]

jack.owns = carpool
jack.put()

bob.owns = carpool
bob.put()

chrysler = Car(brand="Chrysler")
jack.owns.append(chrysler.put())
jack.put()

jackOwns = Car.get(jack.owns)
print >> sys.stdout, "Jack owns: "
for car in jackOwns:
print >> sys.stdout, "- "+car.brand

# Jack owns:
# - VW
# - Chevy
# - Chrysler

whoOwnsTheChevy = Human.gql("WHERE owns = :car",car=chevy)
print >> sys.stdout, "These humans own the Chevy: "
for who in whoOwnsTheChevy:
print >> sys.stdout, "- "+who.name

# These humans own the Chevy:
# - Jack


Powyższe przykłady nie zapewniały unikalności odesłań co może sprawić że dany obiekt może należeć do róznych obiektów tego samego modelu. Zanim dodamy nowy model spawdzimy czy juz istnieje.


from django.db import models
from google.appengine.ext import db

class Car(db.Model):
brand = db.StringProperty(required=True)
wheels = db.ListProperty(db.Key)

class Human(db.Model):
name = db.StringProperty(required=True)
drives = db.ReferenceProperty(reference_class=Car)
spouse = db.SelfReferenceProperty()
owns = db.ListProperty(db.Key)

class Wheel(db.Model):
isBroken = db.BooleanProperty(default=False)
position = db.StringProperty(choices=set(["left_front",
"left_back",
"right_front",
"right_back"]))

class OwnedCar(db.Model):
brand = db.StringProperty(required=True)
owner = db.ReferenceProperty(Human, required=True)

# many-to-many using list+db.Key
jack = Human(name="Jack")
bob = Human(name="Bob")

vw = Car(brand="VW")
chevy = Car(brand="Chevy")

carpool = [vw.put(),chevy.put()]

jack.owns = carpool
jack.put()

bob.owns = carpool
bob.put()

chrysler = Car(brand="Chrysler")
jack.owns.append(chrysler.put())
jack.put()

def unique(lst):
d = {}
for item in lst:
d[item] = 1
return d.keys()

jeep = Car(brand="Jeep")
jack.owns = unique(jack.owns + [jeep.put()])
jack.put()

jackOwns = Car.get(jack.owns)
print >> sys.stdout, "Jack owns: "
for car in jackOwns:
print >> sys.stdout, "- "+car.brand

# Jack owns:
# - VW
# - Chevy
# - Chrysler

whoOwnsTheChevy = Human.gql("WHERE owns = :car",car=chevy)
print >> sys.stdout, "These humans own the Chevy: "
for who in whoOwnsTheChevy:
print >> sys.stdout, "- "+who.name

# These humans own the Chevy:
# - Jack


Możliwości modelowania w GAE są o wiele większe niż to co jest opisane na tych przykładach.

wtorek, 13 maja 2008

Witaj świecie w Google App Engine

Instalujemy Google App Engine SDK Od 28 maja 2008 roku można publikować własne serwisy oparte na Google App Engine.

Po zainstalowaniu można sobie wsiąść się za tworzenie prostej aplikacji w Django. Bo GAE opiera się na silniku frameworka Django. Jeżeli komuś zależy na testowaniu aplikacji w klasycznym Django w Polsce to może wypróbować hosting na megiteam.pl przez 15 dni.

Po zainstalowaniu GAE SDK zrobiłem folder C:\appEngine, następnie utworzymy podkatalog witaj i tak do katalogu C:\appengine\witaj dodałem plik konfiguracyjny app.yaml oraz plik skryptu witaj.py

Ponieważ Google App Engine opiera się na WCGI więc trzeba stworzyć plik konfiguracyjny app.yaml Kod pliku app.yaml:


# nazwa aplikacji
application: appengine
#wersja aplikacji
version: 1
#silnik wykonawczy
runtime: python
#wersja silnika wykonawczego
api_version: 1

#lista adresów URL które maja być obsługiwane
handlers:
- url: /.*
script: witaj.py
# powyżej pokazuje że wszystkie adresy maja
# być obsługiwane przez skrypt witaj.py


Pliki powinny być zakodowane w UTF-8. Oto kod w pliku witaj.py

print 'Content-Type: text/plain'
print ''
print 'Witaj świecie'


W folderze C:\appengine\ zrobimy plik wsadowy start.bat z takim kodem

dev_appserver.py -d witaj


Teraz uruchomiamy plik C:\appengine\start.bat, a potem wpisujemy w przeglądarce internetowej http://localhost:8080/.

Tak wygląda pierwszy krok. Drugi krok to uruchomienie panela administracyjnego
http://localhost:8080/_ah/admin/datastore z przeglądarką danych i z interaktywna konsolą.

Następną aplikację zrobimy na bazie Djangoguestbook. Jest ona w folderze
C:\Program Files\Google\google_appengine\demos\guestbook

Robimy folder C:\appengine\djangoguestbook. W tym folderze dodajemy plik konfiguracyjny app.yaml z takim kodem

application: djangoguestbook
version: 1
runtime: python
api_version: 1

handlers:
- url: .*
script: main.py


Tworzymy plik main.py z takim kodem

import os
import sys
# Google App Engine imports.
from google.appengine.ext.webapp import util

# tworzymy własny plik z ustawieniami
from django.conf import settings
settings._target = None
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'

# Import various parts of Django.
import django.core.handlers.wsgi
import django.core.signals
import django.dispatch.dispatcher
import django.db


def main():
# Wtworzymy aplikacje Django WSGI.
application = django.core.handlers.wsgi.WSGIHandler()

# Uruchamiamy WSGI CGI z ta naszą aplikacja.
util.run_wsgi_app(application)

# konstruktor
if __name__ == '__main__':
main()


Skoro już wiemy co mamy uruchamiać, to następnie trzeba zrobić plik z ustawieniami aplikacji setting.py Oto kod w pliku setting.py:

import os

DEBUG = True
TEMPLATE_DEBUG = DEBUG
APPEND_SLASH = False
LANGUAGE_CODE = 'en-us'
SITE_ID = 1

# If you set this to False, Django will make some
# optimizations so as not
# to load the internationalization machinery.
USE_I18N = True

# URL prefix for admin media -- CSS, JavaScript and images.
# Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/'

# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.load_template_source',
'django.template.loaders.app_directories.load_template_source',
)

MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.doc.XViewMiddleware',
)
# odnośnik do pliku konfiguracyjnego urls.py
ROOT_URLCONF = 'urls'
# odnośnik do folderu w którym jest aplikacja
ROOT_PATH = os.path.dirname(__file__)
#wskazuje na miejsce gdzie są szablony Django
TEMPLATE_DIRS = (
os.path.join(ROOT_PATH, 'templates')
)


Można zauważyć, że odwołujemy się do adresów URL w pliku urls.py, a oto jego kod:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
(r'^$', 'views.index'),
(r'^sign$', 'views.sign'),
)

Widać w nim 2 adresy internetowe: jeden do wyświetlania formularza do wpisywania danych, a drugi do rejestracji, jeśli ktoś nie jest jeszcze zalogowany. Tak więc ustawiliśmy aplikację Django, teraz trzeba zająć się danymi aplikacji czyli jej modelem oraz widokami w szablonach.

Na początek zajmiemy się danymi, które mają być przechowywane w bazie danych na serwerach Google obsługujących GAE. W praktyce lokalny serwer WWW umieszcza swoje dane w plikach. Tworzymy plik model.py

from google.appengine.ext import db

class Post(db.Model):
author = db.UserProperty()
message = db.StringProperty(multiline=True, required=True)
date = db.DateTimeProperty(auto_now_add=True)

Utworzyliśmy tabelę Post z 3 kolumnami (author, message, date) W tej tabeli będą zapisywane dane z formularza i jak odczytywane poprzednie wpisy. Cala logika aplikacji oparta o obsługę widoków jest trzymana w pliku views.py

import datetime

from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response

from google.appengine.api import users

from models import Post
from forms import GuestbookForm

def index(request):
query = Post.gql('ORDER BY date DESC')
form = GuestbookForm()
return render_to_response('index.html',
{'posts': query.fetch(20),
'form': form
}
)

def sign(request):
form = GuestbookForm(request.POST)
if form.is_valid():
post = Post(message=form.clean_data['message'])
if users.GetCurrentUser():
post.author = users.GetCurrentUser()

post.put()

return HttpResponseRedirect('/')


Trzeba zwrócić uwagę na to, że do obsługi zapytań używamy dialektu GQL, który jest podobny do SQL. Nasz aplikacja ma też odrębna klasę wiążącą formularz z danymi czyli klasę GuestbookForm, której kod jest w pliku forms.py

from django import newforms as forms

class GuestbookForm(forms.Form):
message = forms.CharField(label='Sign the Guestbook', widget=forms.Textarea())


Widzimy jak za pomocą modułu Django newforms tworzy się nowe pole formularza, które zostanie użyte w szablonach.

Teraz utworzymy nowy katalog templates. A w katalogu C:\appengine\djangoguestbook\templates dodajemy plik index.html, z takim kodem:



W ten sposób pozostało nam zrobienie skryptu djangoguestbook.bat w katalogu C:\appengine z takim skryptem:

dev_appserver.py -d djangoguestbook


Wystarczy uruchomić ten plik C:\appengine\djangoguestbook.bat
I zobaczyć jak to działa.

niedziela, 11 maja 2008

Pixel Bender

Szukając informacji o tym jakie technologie będą w Flash Player 10. Pomyślałem że jest coś takiego jak Adobe Image Foundation. W kwietniu jakoś po cichu wprowadzono AIF Toolkit Technology Preview 2. W maju Adobe postanowiło zmienić nazwę technologii na Pixel Bender (bo chyba termin Hydra uknuli z mitologii podobnie jak z Apollo). Wcześniejszej wersji nie mogłem przetestować bo była ograniczona do pewnych kart GPU. Obecna wersja pozwala już przetwarzać na procesorach CPU, co daje już możliwości pokazania jak może wyglądać obsługa efektów mieszania w Flash Player 10. Oczywiście że Flash Player ma już wsparcie ze strony GPU jak oglądamy w trybie pełnoekranowym, ale nie mamy wpływu na to jak to działa. Z technologią Pixel Bender będziemy mieli wpływ na obsługę efektów. Od strony technologicznej wygląda na to że zostanie wykorzystane LLVM obsługujące tą technologię.

Czym jest LLVM? Niskopoziomową biblioteką pozwalającą programistom C i C++ wykorzystywać dobrodziejstwa z takich rzeczy jak JIT czy obsługa kodu źródłowego w "locie" wraz z jego weryfikacją. W praktyce można by powiedzieć że wystarczy napisać kod w takim języku niskopoziomowym (coś w stylu asemblera) to przy pomocy odpowiednich narzędzi wygeneruje kod dla danego języka programowania bądź już skompilowaną bibliotekę którą można użyć w innych projektach. Bardziej przystępnej informacji można się dowiedzieć na tej stronie Dość kusząca staje się wizja w którym kod w C/C++ może zostać skonwertowany do postaci języka niskopoziomowego/bajtkodu a taki kod wystarczy potem załadować do Flash Playera. Na razie Adobe wybrało tą technologię do wykorzystywania CPU w Pixel Bender.
Jak to może wyglądać w Flash Player 10? Kod napisany w Pixel Bender jest przekształcany do postaci bajtkodu ABC dzięki LLVM. Następnie dzięki wsparciu ze strony karty graficznej zostaje bajtkod wykorzystywany do generowania efektów. Może Flash Player zechce bezpośrednio przekształcać kod binarny z LLVM to byłaby rewolucja, ze względu na to że skompilowanie kodu źródłowego w C/C++ Flash Playera czy Adobe AIR sprawiłoby, że otrzymalibyśmy takiego samego Flash Playera czy Adobe AIR prawie na wszystkie platformy. To już spekulacja z mojej strony.

Programowanie w Pixel Bender nie należy do łatwych jeżeli ktoś wcześniej nie programował shaderów. Trzeba wejść na stronę i pobrać AIF Toolkit. Zainstalować. Należy przeczytać wprowadzenie (pokazuje przykład jak robić filtr naświetlania - ekspozycji) i plik specyfikacji języka Hydra 1.0 (w AIF Toolkit wystarczy kliknąć CTRL+H)
Jak komuś się to spodoba to może wejść na stronę Galerii gdzie są prezentowane różne przykłady (większość z nich wymaga przepisania na nowo ze względu na zmiany w kodowaniu)
Bardzo ciekawe są przykłady użycia klasycznego mieszania obrazów (teorię można poczytać w tym miejscu) . Zawsze warto mieć w zakładkach blogi ludzi związanych z Pixel Bender - Kevina Goldsmitha i jak Briana Ronana.
Dla niecierpliwych proponuję zapoznanie się z biblioteką ImageProcessing z tym przykładem w Flex 2.

poniedziałek, 5 maja 2008

Adobe Open Screen Project

Duży nacisk ze strony telefonii mobilnej i jak producentów elektroniki użytkowej sprawił, że Adobe musiało wyjść naprzeciw ich oczekiwaniom. Problem polegał na tym że Adobe wie, że coraz więcej rozwiązań technologicznych powstaje w oparciu o licencje Open Source. Trzymając się restrykcyjnie licencji sprawiło by problemy z potencjalnymi klientami którzy potrzebują "własnych" wersji Flash Playerów czy bibliotek o otwartym kodzie. Doświadczenia z producentami telefonów komórkowych pozwoliło Adobe zmienić model biznesowy. Jak sie dąży do opanowania rynku telefonów komórkowych i wejścia w segment elektroniki użytkowej, a dochody z licencjonowania Flash Playera nie są wysokie, to uwolnienie restrykcji licencyjnych może przynieść większy dochód w postaci większego zapotrzebowania na wsparcie i konsultacje w wdrażaniu Flash Playerów przez producentów elektroniki użytkowej.
Adobe w pewien sposób kontroluje sytuację dostarczając użytkownikom Flash Playera i jak narzędzia dla designerów w postaci Flash CS3. Producenci telefonów komórkowych i elektroniki użytkowej doszli do wniosku, że jeżeli chcą dodawać nowe funkcjonalności do FlashLite czy Flash Playera, bo tego chcą konsumenci to muszą wywrzeć wpływ na Adobe, aby pozwoliło razem tworzyć nowe wersje rozwiązań. Na przykład w Symbianie OS czy w UIQ pojawia sie coraz więcej technologii, które wymagają zintegrowania z FlashLite (na przykład posiadają GPS, akcelatory graficzne, ekran dotykowy, bluetooth).

Myślę, że Adobe doszło do wniosku, że pora rozpocząć głęboką fragmentaryzację technologi Flash, pośrednio dążąc do zastąpienia Java Mobile. Technologia J2ME powstała dość dawno i w pewnym sensie midlety były dobrym rozwiązaniem na słabe możliwości ówczesnych urządzeń. A teraz? Programiści i konsumenci chcą rozwiązań na miarę większych możliwości. Pośrednio w tym interes mają zaawansowani użytkownicy, bo nie muszą uczyć się się zawiłości C++, Java Mobile, im wystarczy ze znają Flasha, podstawy Action Scriptu, coś więcej o HTML, CSS i JavaScript. Dzięki temu mogą pokusić się o tworzenie własnych programików na urządzenia mobilne. Widać to na przykładzie popularności iPhone, wystarczyło żeby programiści i zaawansowani użytkownicy pisali i modyfikowali gierki internetowe do iPhone. Ten sukces chciałoby powtórzyć wiele innych firm i stąd naturalną rzeczą wybór padł na FlashLite i Adobe AIR Mobile.

Gdy patrzę bliżej na to co się działo przez ostatnie lata od połączenia się Adobe z Macromedia dochodzę do wniosku, że Adobe może zmierza do standaryzacji swoich technologii. Obecnie mamy 3 instytucje standaryzujace ISO, ECMA, W3C. Ale problem w tym jak umiejscowić standard formatu SWF. Spójrzmy na to co zrobiło Adobe.
Najpierw widzimy Adobe jako prekusora formatu SVG w W3C i wprowadziło własną implementację standardu SVG w postaci wtyczki Adobe SVG Viewer. Ale ten format nie zyskał popularności w wyniku rosnącej popularności technologii Flash.
Adobe brało udział w powstawaniu formatu CSS2, a takze zaproponowało XMP (Extensible Metadata Platform)jako standardu metadanych dla danych graficznych (a ostatnio dla danych typu video) na bazie standardu RDF.
Natomiast w kolejnej instytucji standaryzujacej ECMA Adobe ma wpływ na
ECMA-262 ECMAScript (standardised JavaScript)
ECMA-357 ECMAScript for XML (E4X)
ECMA-363 Universal 3D file format (jest jako dodatek do Adobe Readera)
Myślę że nie wiele by brakowało do ustalenia formatu SWF jako standardu ECMA ( jako standardu dla multimediów)

A ostatnia instytucja standaryzująca to ISO. W niej Adobe czuje się silne dzięki opublikowaniu standardu PDF. Podsumowując Adobe woli koegzystencję z Microsoftem, dzięki czemu może liczyć na wsparcie ze strony Microsoftu w pracach nad standardami. Jak można zauważyć mamy kilka standardów odnośnie danej technologii (dla wydruku to XPS w ECMA, a PDF w ISO czy dla modeli trójwymiarowych U3D w ECMA i X3D w ISO czy nieformalnie Collada, w formatach dokumentu elektronicznego OOXML w ECMA i ISO oraz OpenDocument w ISO, a także w zakresie multimediów dla internetu (SVG, CSS, SMIL, PNG wg W3C a JPEG, JPEG2000, MPEG-4 w tym GFX, AFX, OpenType, BIFS, AVC/H.264 wg ISO)

Pozostaje tylko czekać jak format SWF stanie się formalnym standardem ( a dokładniej jako format ABC to binarny zapis dla JavaScript 2 i EX4).

Podsumowując można spodziewać sie fragmentaryzacji platformy Flash w tym kierunku konsorcjum takiej jak Java Specification Request. Warto poczytać o Open Screen Project a także Adobe Open Source