Sprechblasen mit HTML5, Canvas und JavaScript

Mit HTML5, Canvas und JavaScript kann man mittlerweile eine Menge schöner Grafiken direkt im Browser rendern. Da ich neulich eine flexible Sprechblase benötigt habe, hier der Code dafür:

/**
 * Draw a speech bubble on an HTML5-Canvas
 *
 * @author Alexander Thiemann 
 * @license CC BY-SA 2.0
 * 
 * @param ctx Canvas 2d context
 * @param text Text for the speech bubble
 * @param x bottom left x-coordinate of speech bubble
 * @param y bottom left y-coordinate of speech bubble
 *
 */
function speechBubble(ctx, text, x, y) {
	var messure = ctx.measureText(text);
	
	var w = messure.width;
	var h = 20;
	
	ctx.beginPath();
	ctx.strokeStyle="black";
	ctx.lineWidth="1";
	ctx.fillStyle="rgba(255, 255, 255, 0.8)";
	
	ctx.moveTo(x, y);
	ctx.lineTo(x + (w*0.2), y);
	ctx.lineTo(x + (w*0.2), y+10);
	ctx.lineTo(x + (w*0.3), y);
	ctx.lineTo(x + w, y);
	
	ctx.quadraticCurveTo(x + (w*1.1), y, x + (w*1.1), y-(h*0.2)); // corner: right-bottom
	
	ctx.lineTo(x + (w*1.1), y-(h*0.8)); // right
	
	ctx.quadraticCurveTo(x + (w*1.1), y-h, x + w, y-h); // corner: right-top
	
	ctx.lineTo(x, y-h); // top
	
	ctx.quadraticCurveTo(x - (w*0.1), y-h, x - (w*0.1), y-(h*0.8)); // corner: left-top
	
	ctx.lineTo(x - (w*0.1), y-(h*0.2)); // left
	
	ctx.quadraticCurveTo(x - (w*0.1), y, x, y); // corner: left-bottom
	
	ctx.fill();
	ctx.stroke();
	ctx.closePath();
	
	ctx.textAlign = 'left';
	ctx.fillStyle = '#000';
	ctx.fillText(text, x, y-6);
}

Verwendung:
HTML


JavaScript

var ctx = document.getElementById("bubble").getContext('2d');
speechBubble(ctx, "Das hier ist ein Test!", 5, 40);

Das Resultat:
Realisiert mit HTML5, Canvas und JavaScript

Ziemlich schick, oder? 😉

 

Zippyshare Downloads automatisieren [reloaded]

Heute geht es wieder um Zippyshare-Downloads, bzw. wie man diese einfach automatisieren kann. Da seit meinem letzten Beitrag zu diesem Thema die Seite wieder einige Umstellungen gemacht hat, präsentiere ich hier meine neue AutoIT Lösung:

; #FUNCTION# ====================================================================================================================
; Name ..........: _ZippyLoad
; Description ...: Download a file from zippyshare.com
; Syntax ........: _ZippyLoad($sUrl, $sFilename)
; Parameters ....: $sUrl                - Downloadpage, eg. http://www01.zippyshare.com/view.jsp?locale=de&key=12345678 or http://www01.zippyshare.com/v/12345678/file.html
;                  $sFilename           - Path to where the download should be saved, eg. C:test.mp3
; Return values .: False on error, InetGet-Handle on success
; Author ........: www.agrafix.net
; Modified ......: 17.11.2011 - 15:57
; Remarks .......: None
; Related .......: _ZippyIsComplete
; Link ..........: http://blog.agrafix.net/tag/zippyshare/
; Example .......: No
; License .......: CC BY-NC 3.0
; ===============================================================================================================================
Func _ZippyLoad($sUrl, $sFilename)
	; RegExp
	$sRegExp  = ''
	$sRegExp &= 's*var a = ([0-9]*)%([0-9]*);'
	$sRegExp &= 's*var b = ([0-9]*)%([0-9]*);'
	$sRegExp &= 's*document.getElementById(.dlbutton.).href = "([^"]*)"+(([ab*+-/0-9]*))+"([^"]*)";'
	$sRegExp &= 's*'

	; Extract Host
	$aHostMatches = StringRegExp($sURL, 'http://([^/]*)', 1)
	$sHost = $aHostMatches[0]

	If @error = 1 Or @error = 2 Then
		SetError(1)
		Return False ; Invalid Host
	EndIf

	; Get Downloading Page
	$sSource = _INetGetSource($sURL)

	; Extract Params for Download-URL
	$aMatches = StringRegExp($sSource, $sRegExp, 1)

	If @error = 1 Or @error = 2 Then
		SetError(2)
		Return False ; Downloading failed
	EndIf

	$iVarA = Mod($aMatches[0], $aMatches[1])
	$iVarB = Mod($aMatches[2], $aMatches[3])

	$sMathExpr = StringReplace(StringReplace($aMatches[5], "a", $iVarA), "b", $iVarB)

	$iResult = Execute($sMathExpr)

	$sBaseUrl = $aMatches[4] & $iResult & $aMatches[6]

	$sFullPath = "http://" & $sHost & $sBaseUrl

	; Download File
	$hHandle = InetGet($sFullPath, $sFilename, 1, 1)
	ConsoleWrite("Downloading " & $sFullPath & " to " & $sFilename & @CRLF)

	Return $hHandle
EndFunc

; #FUNCTION# ====================================================================================================================
; Name ..........: _ZippyIsComplete
; Description ...: Check if a download launched with _ZippyLoad is complete
; Syntax ........: _ZippyIsComplete($hHandle)
; Parameters ....: $hHandle             - the handle returned from _ZippyLoad
; Return values .: True if download is complete, False if not
; Author ........: www.agrafix.net
; Modified ......: 17.11.2011 - 15:57
; Remarks .......: None
; Related .......: _ZippyLoad
; Link ..........: http://blog.agrafix.net/tag/zippyshare/
; Example .......: No
; License .......: CC BY-NC 3.0
; ===============================================================================================================================
Func _ZippyIsComplete($hHandle)
	Return InetGetInfo($hHandle, 2)
EndFunc

Die Verwendung dieser Funktionen ist denkbar einfach:

#include 

$h = _ZippyLoad("http://www01.zippyshare.com/view.jsp?locale=de&key=12345678", @ScriptDir & "test.mp3")
While Not _ZippyIsComplete($h)
	Sleep(250)
WEnd
ConsoleWrite("Download complete..." &@CRLF)

Der Code steht zur freien Verwendung unter der CC BY-NC 3.0 Lizenz.

 

Manager’s Life V2

Die Entwicklung von Manager’s Life V2 geht langsam auf ein Release zu – deshalb wird die aktuelle Version am 25.11.2011 beendet! Wir bedanken uns bei allen, die bisher das Spiel gespielt haben und würden uns freuen euch wieder bei Version 2 begrüßen zu dürfen.

Das alte Spielkonzept wird in Version 2 komplett über den Haufen geworden und das Spiel wurde von Grund auf neu Programmiert. Erhalten bleibt eigentlich nur das Design! Ich möchte jetzt noch nicht zu viel verraten, aber es wird aufjedenfall ein Online-Spiel, von dem es in dieser Form erst sehr wenige gibt. Es nutzt die modernen HTML5 und JavaScript Techniken komplett aus, ist sehr grafisch und funktioniert auch auf mobilen Endgeräten.

Lasst euch überraschen!

Wer einen Beta-Key möchte, um in der Closed Beta mitzuspielen, schickt bitte eine Email an allypage@gmail.com. Die Keys sind jedoch nur sehr begrenzt erhältlich!

Manager’s Life

 

AJAX und der „Vor“ und „Zurück“ Button

Kleiner Hinweis: Dieser Blogpost nimmt an, das jQuery verwendet wird 😉

Dank AJAX kann man eine Menge Traffic einsparen – allerdings hat AJAX leider ein paar Nachteile: Die Browsernavigation, also die „Vor“ und „Zurück“ Knöpfe des Browsers, funktionieren nicht mehr.
Hier mal ein Beispiel:

HTML:

Inhalt ändern
Hier ist Inhalt 1

JavaScript:

$("a").live("click", function (e) {
    e.preventDefault();
    
    $('#content').load(e.target.href, function() {
        // nothing here yet ;)
    });
});

Klickt man nun auf „Inhalt ändern“, erscheint der Inhalt von content.html im content-div. Wenn jetzt der Benutzer allerdings wieder „Hier ist Inhalt 1“ sehen möchte, kann er nicht den „Zurück“-Button seines Browsers verwenden.

Um dieses Problem zu lösen gibt es zwei Möglichkeiten:

  • Einen hash an die aktuelle URL anhängen, etwa so: /#!content.html
  • Das HTML5 window.history-Objekt bemühen

Da die zweite Methode leider (noch) nicht von jedem Browser unterstützt wird, aber die wesentlich hübschere Methode ist, habe ich mir ein Objekt geschrieben, was beides kann!

/**
 * navigatorHistory
 *
 * Fixes navigator history for ajax-based sites
 *
 * @author agrafix 
 */
var navigatorHistory = {
	/**
	 * Title of site
	 */
	siteTitle: '',
	
	/**
	 * function to load content, needs to be set before hook()-call
	 */
	loaderFunc: null,

	/**
	 * check if the browser supports pushState
	 */
	isSupported : function() {
		return (typeof (window.history.pushState) == 'function');
	},

	/**
	 * add url to history
	 */
	add : function(url) {

		if (this.isSupported()) {
			
			window.history
					.pushState(null, this.siteTitle, url);
		} else {
			window.location.hash = "#!" + url;
		}
	},

	/**
	 * listen for back-forward button events
	 */
	hook : function() {
		if (!this.loaderFunc) {
			alert("No loaderFunc defined!");
			return;
		}
	
		if (this.isSupported()) {
			// doesn't work with jquery, no idea why :O
			window.addEventListener("popstate", function(e) {
				navigatorHistory.loaderFunc(document.location.pathname, true);
			});
		} else {
			$(window).bind('hashchange', function() {
				var location = (window.location.hash).replace(/^#!/, '');
				navigatorHistory.loaderFunc(location, true);
			});
		}

	}
};

Die Benutzung in Verbindung mit dem obigen HTML-Schnipsel ist denkbar einfach:

// funktion zum laden der Seiten definieren
navigatorHistory.loaderFunc = function(url, nohistory) {
	if (!nohistory) {
		navigatorHistory.add(url);
	}
	
	$('#content').load(url);
};

// seiten titel
navigatorHistory.siteTitle = "Deine Seite";

// auf vor bzw. zurück-button klicks hören
navigatorHistory.hook();

// die eigentliche ajax-funktion
$("a").live("click", function (e) {
    e.preventDefault();
	
	navigatorHistory.loaderFunc(e.target.href);
});

Eigentlich relativ einfach einzubauen und es macht eine Seite deutlich benutzerfreundlicher!

 

Unix-Timestamp in JavaScript formatieren

Um ein Unix-Timestamp mit JavaScript zu formatieren, habe ich mir folgendes Objekt geschrieben:

var timeFormat = {
    /**
     * Output format.
     *
     * d - current day
     * m - current month
     * Y - current Year
     *
     * H - current hour (24 hour system)
     * i - current minute
     * s - current second
     */
    displayFormat: 'd.m.Y - H:i:s',

    /**
     * Format given unix-timestamp
     */
    format: function(timestamp) {
        // convert unix timestamp to javascript date object
        var d = new Date(timestamp * 1000);
        var output = this.displayFormat;
        
        output = output.replace(/d/g, this.padZero(d.getDate()))
            .replace(/m/g, this.padZero(d.getMonth()+1))
            .replace(/Y/g, d.getFullYear())
            .replace(/H/g, this.padZero(d.getHours()))
            .replace(/i/g, this.padZero(d.getMinutes()))
            .replace(/s/g, this.padZero(d.getSeconds()));

        
        return output;

    },
   
    /**
     * add zero paddings to numbers smaller than 10
     */
    padZero: function(number) {
        if (number < 10) {
            return "0" + number.toString();
        }
        
        return number;
    }
};

Die Benutzung ist ziemlich einfach:

// schreibt 13.09.2011 - 13:30:23
document.write(timeFormat.format(1318473023));

Man kann das Ausgabeformat über das timeFormat.displayFormat Feld steuern:

timeFormat.displayFormat = 'H:i'; // gibt nur Stunden und Minuten aus
document.write(timeFormat.format(1318473023)); // schreibt 13:30

Wie bereits oben beschrieben sind folgende Zeichen möglich (auch mehrfach)

    /**
     * Ausgabeformat
     *
     * d - aktueller Tag
     * m - aktueller Monat
     * Y - aktuelles Jahr
     *
     * H - aktuelle Stunde
     * i - aktuelle Minute
     * s - aktuelle Sekunde
     */
 

JavaScript Timers mit jQuery

Timer in JavaScript zu realisieren ist eigentlich recht leicht, dazu braucht mal nur die Funktionen window.setTimeout bzw. window.setInterval mit entsprechenden Argumenten zu füttern.

// funktion verzögert aufrufen
function machWas() {
   alert('alert() mit 1000ms verzögerung!');
}
window.setTimeout(machWas, 1000);

// funktion alle X Millisekunden aufruden
function machWasOft() {
   alert('alle 1000ms ein alert()');
}
window.setInterval(machWasOft, 1000);

Doch was ist, wenn ich jetzt einen solchen Timer ändern oder abbrechen möchte? Das ist eigentlich auch nicht schwer – die Funktionen setTimeout/setInterval geben ein Objekt zurück über das sie sich wieder beenden lassen.

var x = 0;
function machWasAbbrechbarOft() {
    x += 1;
    if (x > 200) {
        window.clearInterval(running); // beende das "Interval" nach 201 Ausführungen
    }
}
var running = window.setInterval(machWasAbbrechbarOft, 1000);

Das funktioniert in kleinen JavaScript Codes sicher ohne Probleme, wenn man allerdings an einem großem JavaScript Projekt arbeitet, macht es die Arbeit nicht gerade leichter wenn man immer dieses Rückgabe-Objekt (im Beispiel running) irgendwo speichern muss, vorallem wenn man von überall im Code darauf zugreifen will.

Um dieses Problem zu lösen habe ich ein jQuery Timer Plugin gefunden. (Siehe http://plugins.jquery.com/project/timers) Wer an einem größerem JavaScript Projekt arbeitet, sollte meiner Meinung nach aufjedenfall auf ein Framework setzen – jQuery ist hier meine Wahl von daher bietet sich dieses Plugin an.

Mit diesem Plugin geht das „Interval“ setzen dann wie folgt:

$(document).everyTime(1000, "machWasOftAbbrechbar", function(i) { // alle 1000ms
  alert('Hallo ich bin der ' + i + 'te Aufruf!'); // machWasOftAbbrechbar, hier die funktion die aufgerufen werden soll
}, 200); // 200 mal ausführen

Der zweite Parameter, machWasOftAbbrechbar, stellt hierbei ein sogenanntes „label“ dar, über das man den gestarten Timer dann wieder abbrechen kann:

$(document).stopTime("machWasOftAbbrechbar"); // stoppe machWasOftAbbrechbar

Viel mehr gibt’s eigentlich nicht dazu zu sagen – die Dokumentation dazu findet man, genau wie das Plugin selbst, unter http://plugins.jquery.com/project/timers.

 

JavaScript und Geolocation

Seit Firefox 3.5, Opera 10.60 bzw. Safari/Webkit 5 ist es möglich per JavaScript mit Einverständniss des Benutzers seine Position zu ermitteln. Die Position wird dann entweder über eine angeschlossene GPS-Antenne oder über die verfügbaren WLans ermittelt. Die API dafür ist sehr leicht zu benutzen.

Zunächst sollte geprüft werden ob der benutzte Browser Geolocation unterstützt. Das geht wie folgt:

if (!navigator.geolocation) {
	alert('Sorry, dein Browser unterstützt keine Geolocation-API');
}

Jetzt ist es möglich die aktuelle Position abzufragen:

navigator.geolocation.getCurrentPosition(function(position) {  
	alert('Deine aktuelle Position ist: Lat: ' + position.coords.latitude + ' Long: ' + position.coords.longitude);  
}, function(error) { alert('Fehler beim finden der Position. Fehler: ' + error.message); });

und bei jeder Änderung der Position erneut ein Event auszulösen:

var = locationHandler = navigator.geolocation.watchPosition(function(position) {  
	alert('Deine neue Position ist: Lat: ' + position.coords.latitude + ' Long: ' + position.coords.longitude); 
}, function (e) {}, {enableHighAccuracy:true, maximumAge:120 * 1000, timeout:27000});

Hierbei kann man angeben

  • enableHighAccuray: ob man auf hohe Genauigkeit setzen möchte (sofern diese vom Gerät unterstützt wird)
  • maximumAge: (in ms) wie alt die letzte Position maximal sein darf – je kleiner dieser Wert, desto öfterst wird auf dem Gerät die aktuelle Position abgefragt.
  • timeout: (in ms) wann die Positionsbestimmung abgebrochen werden soll wenn sie nicht erfolgreich ist

Wenn man die Positionsüberwachung wieder abbrechen will, hilft folgender API-Call weiter:

navigator.geolocation.clearWatch(locationHandler);

Sehr nützlich, vorallem wenn man eine Webseite für Smartphones bastelt 😉

 

Abstand von zwei GPS-Koordinaten (SQL Funktion)

Für mein aktuelles Projekt muss meine Datenbank den Abstand zwischen zwei GPS-Koordinaten berechnen. Dies Funktioniert ganz einfach mit meiner folgenden Funktion GPS_DISTANCE:

DELIMITER |;
CREATE FUNCTION GPS_DISTANCE(lat1 DOUBLE, long1 DOUBLE, lat2 DOUBLE, long2 DOUBLE)
	RETURNS DOUBLE
	DETERMINISTIC
		BEGIN
			DECLARE dist DOUBLE;
			SET dist = ACOS(SIN(RADIANS(lat1)) * SIN(RADIANS(lat2)) + COS(RADIANS(lat1)) * COS(RADIANS(lat2)) * COS(RADIANS(long2)-RADIANS(long1))) * 6371 * 1000;
			RETURN dist;
		END|

Die Parameter sind jeweils die GPS-Länge und die GPS-Breite (in Grad). Der Rückgabewert ist der Abstand in Metern.

 

Mit PHP einen simplen Captcha cracken

Solange ein Captcha nicht sehr aufwändig generiert wurde, ist er auch entsprechend leicht zu knacken. Ich habe hier mal ein Beispiel für einen schlechten Captcha:

Ein Beispiel für einen schlechten Captcha

Was ist an diesem Captcha schlecht? Der auszulesende Inhalt ist nicht abwechselnd genug (immer nur Zahlenfolgen), nicht verzerrt, nicht farblich variierend und hebt sich zu gut vom Hintergrund ab. Das alles werden wir nun ausnutzen um den Captcha zu knacken.

Zuerst müssen wir „Zahlendefinitionen“ schreiben. Das heißt wir legen für jede Zahl einige eindeutige Fixpunkte fest, mit denen die Zahl eindeutig identifizierbar ist.

Beispiel an der Zahl „1“:

     OO             
    OOO             
   OOOO             
     OO             
     OO             
     OO             
     OO             
     OO             
     OO             
   OOOOOO           

Die Definition:

function is_one($pix, $x, $y) {
	$c = 0;
	$c += check($pix, $x, $y, 2, -2); // 5 / 5
	$c += check($pix, $x, $y, 3, -2); // 6 / 5
	$c += check($pix, $x, $y, 1, -1); // 4 / 6
	$c += check($pix, $x, $y, 2, -1); // 5 / 6
	$c += check($pix, $x, $y, 3, -1); // 6 / 6
	$c += check($pix, $x, $y, 0, 0); // 3 / 7
	$c += check($pix, $x, $y, 1, 0); // 4 / 7
	$c += check($pix, $x, $y, 2, 0); // 5 / 7
	$c += check($pix, $x, $y, 3, 0); // 6 / 7
	$c += check($pix, $x, $y, 2, 1); // 5 / 8
	$c += check($pix, $x, $y, 3, 1); // 6 / 8
	$c += check($pix, $x, $y, 2, 2); // 5 / 9
	$c += check($pix, $x, $y, 3, 2); // 6 / 9
	$c += check($pix, $x, $y, 2, 3); // 5 / 10
	$c += check($pix, $x, $y, 3, 3); // 6 / 10
	$c += check($pix, $x, $y, 2, 4); // 5 / 11
	$c += check($pix, $x, $y, 3, 4); // 6 / 11
	$c += check($pix, $x, $y, 2, 5); // 5 / 12
	$c += check($pix, $x, $y, 3, 5); // 6 / 12
	$c += check($pix, $x, $y, 2, 6); // 5 / 13
	$c += check($pix, $x, $y, 3, 6); // 6 / 13
	$c += check($pix, $x, $y, 0, 7); // 3 / 14
	$c += check($pix, $x, $y, 1, 7); // 4 / 14
	$c += check($pix, $x, $y, 2, 7); // 5 / 14
	$c += check($pix, $x, $y, 3, 7); // 6 / 14
	$c += check($pix, $x, $y, 4, 7); // 7 / 14
	$c += check($pix, $x, $y, 5, 7); // 8 / 14
	if ($c == 27) {
		return true;
	}
	return false;
}

Dort sind nun alle Fixpunkte relativ zum „ersten“ Fixpunkt gespeichert. Der „erste“ Fixpunkt ist bei der eins der Punkt ganz unten rechts. Nachdem wir solche Definitionen für alle Zahlen von 0-9 erstellt haben geht’s nun an die Wiedererkennung der Zahlen. Dazu muss zuerst der Hintergrund herausgefiltert werden und die Grafik in ein besser (für unser Programm) lesbares Format gebracht werden. Dies mache ich wie folgt:

function read($path) {
	$im = ImageCreateFromPNG($path);
	$pix = array();
	$sy = ImageSy($im);
	$sx = ImageSx($im);

	for ($y = 0;$y<$sy;$y++) {
		for ($x = 0;$x<$sx;$x++) {
			$col = imagecolorat($im, $x, $y);
			$rgb = imagecolorsforindex($im, $col);

			if ($rgb["red"] <= 150) {
				$pix[$x][$y] = "O";
			}
			else {
				$pix[$x][$y] = "W";
			}
		}

	}

	// ...
}

Nun haben wir den Array $pix, der bei [x][y] entweder W (für weiß) oder O (für schwarz) enthält. Jetzt müssen wir nur noch durch den Array durchgehen und nach unseren Fixpunkt-Mustern suchen:

        // ....

        $no = 0;
	$complete_string = "";
	$found_at = array();
	
	for ($x = 0;$x<$sx;$x++) {
		for ($y = 0;$y<$sy;$y++) {
			if ($pix[$x][$y] == "O" && !in_array("$x|$y", $found_at)) {
				$no = 0;
				
				if (is_one($pix, $x, $y)) {
					$no = 1;
				}
				if (is_two($pix, $x, $y)) {
					$no = 2;
				}
				if (is_three($pix, $x, $y)) {
					$no = 3;
				}

				if (is_four($pix, $x, $y)) {
					$no = 4;
				}

				if (is_five($pix, $x, $y)) {
					$no = 5;
				}

				if (is_six($pix, $x, $y)) {
					$no = 6;
				}

				if (is_seven($pix, $x, $y)) {
					$no = 7;
				}

				if (is_eight($pix, $x, $y)) {
					$no = 8;
				}

				if (is_nine($pix, $x, $y)) {
					$no = 9;
				}
				
				if ($no != 0) {
					$found_at[] = "$x|$y";
					$complete_string .= $no;
				}
			}
		}
	}

	//...

Die Variable $complete_string enthält nun den ausgelesenen String. Simple as that 😉

Hier der gesamte Code inklusive der Hilfsfunktion check(). Der Code ist schon etwas älter, also nicht über schlechten Code-Stil wundern – er soll legendlich das Prinzip erklären: http://agrafix.net.pastebin.com/b6exg0SW

Noch ein Screenshot:
Geknackter Captcha

Das war’s schon – viel Vergnügen damit!

 

Zippyshare Downloads Automatisieren *update*

Kurz nachdem ich den letzten Beitrag über zippyshare veröffentlicht habe, hat die Seite wieder umgestellt auf einen anderen „Schutz“ der Downloadlinks. Diesmal ist es aber ziemlich einfach an diese zu geraten, man muss nur nach documents.getElementById(‚dlbutton‘).href = „…“ suchen. Dort findet sich in Klammern ein einfacher mathematischer Ausdruck, der in fasst jeder Programmiersprache mit einem Einzeiler ausgeführt werden kann.