Vorüberlegungen
Da Javascript bekanntlich keine über keine Methoden zur Erstellungen von Dateien verfügt, müssen wir uns Gedanken machen, wie wir eine Grafik ohne zu speichern anzeigen können. Dabei hilft uns das data: URI-Schema weiter. (http://en.wikipedia.org/wiki/Data:_URI_scheme). Es ermöglicht uns eine Grafik in die HTML-Datei einzubetten. Das funktioniert allerdings (leider) nur im Mozilla (Firefox) richtig gut. Im Opera ist die Dateigröße auf 4096Byte begrenzt, und im InternetExplorer fehlt das URI-Schema komplett. Deswegen beschränkt sich der rest des Tutorials auf den Mozilla-Browser.
Das URI data: Schema ist wie folgt aufgebaut: „data:<mime-type>;base64,<data>“. Das heißt also, dass unsere Daten im base64-Format vorliegen müssen. (http://de.wikipedia.org/wiki/Base64). Auf die Umwandlung von Binärdaten nach Base64 werde ich im folgenden nicht genauer eingehen, Google und die Wiki bieten dort Hilfe. Da wir zur Umwandlung aufgrund von nötigen Bit-Operationen mit Zahlen arbeiten, werden wir unsere Grafik auch nicht als String generieren, sondern direkt in Zahlen (also direkt die Dezimalwerte unserer Bytes).
Zusammengefasst also: Pixeldaten -> Bitmap als Zahlenarray -> Bitmap in Base64 kodiert -> URI data: Schema
Eine einfache Image-Klasse für Pixelgrafiken
Unsere Grundklasse soll alles Notwendige für eine Grafik beinhalten: Informationen über die Breite, die Höhe und die Pixeldaten in einem zweidimensionalen Array. Also fangen wir mal an:
PHP-Code:
function PixelImage( width, height ) {
this.getWidth = function() {
return this.mWidth;
};
this.getHeight = function() {
return this.mHeight;
};
this.getRow = function( y ) {
return this.mImageRows[y];
};
// unser constructor erstellt das bild und füllt es mit nullen
this.mWidth = width;
this.mHeight = height;
this.mImageRows = [];
for( var y = 0; y < height; y++ ) {
var row = [];
for( var x = 0; x < width; x++ ) {
row.push( 0 );
}
this.mImageRows.push( row );
}
}
PHP-Code:
var image = new PixelImage( 64, 256 );
for( var y = 0; y < image.getHeight(); y++ ) {
var row = image.getRow( y );
for( var x = 0; x < image.getWidth(); x++ ) {
row[ x ] = (y << 16) | ((255 - y) << 8);
}
}
Ein kleiner Helfer
Beim Schreiben der Bitmap Datei wird es vorkommen, dass wir nicht nur ByteWeise, sondern auch mal einen short (WORD, 2byte) oder einen int(DWORD, 4byte) schreiben müssen. Deswegen erweitern wir die Array-Klasse um entsprechende Funktionen, die einen Multi-Byte-Wert nimmt und entsprechend zerlegt als einzelne Bytes abspeichert. Bitmaps werden Little-Endian abgespeichert. (http://de.wikipedia.org/wiki/Little_Endian)
PHP-Code:
function BinaryArray() {
var array = [];
array.pushInt = function( i ) {
this.push( i & 0xff );
this.push( (i >> 8) & 0xff );
this.push( (i >> 16) & 0xff );
this.push( (i >> 24) & 0xff );
};
array.pushShort = function( i ) {
this.push( i & 0xff );
this.push( (i >> 8) & 0xff );
};
array.pushChar = function( c ) {
this.push( c.charCodeAt( 0 ) & 0xff );
};
array.toString_Base64 = function() {
// werde ich hier nicht näher beschreiben
// ist im Beispiel zu finden.
return it;
};
return array;
}
Um eine Bitmap-Datei schreiben zu können, müssen wir uns erstmal das Format einer Bitmap-Datei angucken. Der Wikipedia-Artikel (http://de.wikipedia.org/wiki/Windows_Bitmap) hilft auch diesmal.
Eine Bitmap besteht aus zwei Headern und dahinter den unkomprimierten Daten. Der erste Header, auch Bitmap-File-Header genannt umfasst vier Eigenschaften. Das erste (ein WORD, also 2Byte ) hat immer die Zeichenkette "BM". Daraufhin folgt die Dateigröße (DWORD, also 4Byte), ein reserviertes DWORD und die Position, an denen die Daten starten (54bytes) (DWORD).
Man muss allerdings bei der Berechnung der Werte etwas acht geben: Die Bytelänge der Zeilen einer Bitmap müssen immer vielfache von 4 sein.
Schreiben wir also eine Bitmapklasse und eine Funktion, die unseren Dateikopf schreibt (writeBitmapFileHeader):
PHP-Code:
function Bitmap( image ) {
this.writeBitmapFileHeader = function( bitmap, filesize ) {
bitmap.pushChar( "B" );
bitmap.pushChar( "M" );
bitmap.pushInt( filesize );
bitmap.pushInt( 0 );
bitmap.pushInt( 54 );
};
this.mImage = image;
}
PHP-Code:
this.writeBitmapInfoHeader = function( bitmap, width, height, size ) {
bitmap.pushInt( 40 ); // Größe des BitmapInfoHeaders
bitmap.pushInt( width ); // Breite des Bildes
bitmap.pushInt( height ); // Höhe des Bildes
bitmap.pushShort( 1 ); // Wird nicht verwendet, sollte 1 sein
bitmap.pushShort( 24 ); // Farbtiefe. Unsere Bitmap hat 24bit (3 Kanäle a 8bit)
bitmap.pushInt( 0 ); // Komprimierung ( 0=unkomprimiert)
bitmap.pushInt( size ); // Anzahl der Pixel-Bytes, die nach dem InfoHeader folgen
bitmap.pushInt( 0 ); // Pixel pro Meter X (wird meistens 0 gesetzt)
bitmap.pushInt( 0 ); // Pixel pro Meter Y (wird meistens 0 gesetzt)
bitmap.pushInt( 0 ); // Anzahl der Einträge in der Farbtabelle. ( 0=keine Tabelle)
bitmap.pushInt( 0 ); // 0, weil das letzte Feld auch 0 ist.
};
PHP-Code:
this.writeBitmapData = function( bitmap, width, height ) {
for( var y = height - 1; y >= 0; y-- ) {
var pixels = this.mImage.getRow( y );
for( var x = 0; x < width; x++ ) {
var i = pixels[ x ];
bitmap.push( i & 0xff ); // Blau zuerst
bitmap.push( (i >> 8) & 0xff ); // dann Grün
bitmap.push( (i >> 16) & 0xff ); // dann Rot
}
// Die Länge einer jeden Zeile muss einem Vielfachen von 4 entsprechen.
if( (width * 3) % 4 ) {
for( var pad = (width * 3) % 4; pad < 4; pad++ ) {
// sonnst mit Nullen füllen
bitmap.push( 0 );
}
}
}
};
PHP-Code:
this.toString = function() {
var width = this.mImage.getWidth();
var height = this.mImage.getHeight();
// Größe in Bytes einer Zeile. (Vielfaches von 4)
var rowSize = Math.ceil( (width * 3) / 4 ) * 4;
var imageSize = rowSize * height; // Größe der Pixeldaten der Bitmap ins Bytes
var fileSize = imageSize + 54; // Größe der Bitmap inklusive Header
var bitmap = new BinaryArray();
this.writeBitmapFileHeader( bitmap, fileSize );
this.writeBitmapInfoHeader( bitmap, width, height, imageSize );
this.writeBitmapData( bitmap, width, height );
return bitmap.toString_Base64();
};
Um die Bitmap Datei auszugeben, müssen wir nur noch unseren kleinen "URI data: Schema"-Prefix nehmen und vor unsere Bitmap einem src Attribute einer Grafik zuordnen. Sagen wir "myImage" sei die id eines <img> Tags in der Seite.
PHP-Code:
var bytes = new Bitmap( image ).toString();
var image_tag = document.getElementById( "myImage" );
image_tag.src = "data:image/x-ms-bmp;base64," + bytes;
Das ganze System ist relativ langsam, Javascript ist aber schließlich auch nicht auf so etwas ausgelegt. Wer Vorschläge zur Optimierung hat, mag die mir gerne mitteilen.
Im Anhang befindet sich auch noch ein kleines Anwendungsbeispiel, wo ich die Klassen dazu benutze, die Mandelbrotmenge (ein Fraktal) zu berechnen.
Viel Spaß, Olli



Kommentar schreiben

Bereiche
Kategorien
Forum - Webmaster & Internet





Artikel bewerten