guenni
Goto Top

Bilder in DB speichern und via PHP-GD ausgeben

Betriebssystem: Linux
Webserver: Apache
Datenbank: MySQL
Scriptsprache: PHP

Bilder hochladen, kodieren und in DB speichern.

Um Bilder in einer DB zu speichern, kann man in einer Abfrage ja nicht einfach den Dateinamen angeben, sondern das Bild wird

z.B. mittels eines Formulars hochgeladen. Natürlich war jetzt meine Überlegung, wenn ich ein Bild sowieso hochladen muß,

um es in einer DB zu speichern, dann kann ich doch beides miteinander kombinieren: Upload und/oder Speichern in einer DB

Herausgekommen ist ein Upload-Script mit folgenden Funktionen:

- Das Script speichert Bilder in einem Verzeichnis des Webservers
- Optional kann noch ein Unterverzeichnis angegeben werden
- Via Checkbox kann man entscheiden, ob Bilder nach dem Hochladen noch kodiert und in einer DB gespeichert werden
- Sollen Bilder nur im der DB gespeichert werden, können Bilder nach Upload und Kodierung wieder gelöscht werden

Ein eventuell angelegtes Unterverzeichnis wird nicht gelöscht, da es ja auch noch mehr Bilder und/oder Daten enthalten könnte.

Zusätzlich kann ein Copyright über das Bild gelegt werden, entweder in Form eines einfachen Text und/oder als transparentes Wasserzeichen.

Der Text kann hierbei ein- oder mehrzeilig sein. Soll der Text mehrzeilig sein, so muß ein Array erstellt werden.

Beispiele hierzu finden sich im Script weiter unten.

Verwendete Software auf dem Server

Betriebssystem: Linux
Webserver: Apache
Datenbank: MySQL
Scriptsprache: PHP

Zum Zugriff auf die Datenbank verwende ich PDO. Um aus den kodierten Bilddaten "wieder ein Bild zu machen", wird die GD-Bibliothek benötigt.

Um nachzusehen, ob beides zur Verfügung steht, einfach eine PHP-Seite erstellen, und dort die Funktion phpinfo() aufrufen.

Die Datenbank zum Speichern der Bilddaten

Meine Datenbank zur Aufnahme der kodierten Bilddaten besteht aus einer Tabelle mit lediglich zwei Spalten

- id vom Typ int mit auto_increment
- data vom Typ Text

Upload-Script

Im Upload-Script wird das Root-Verzeichnis des Webservers ermittelt: $pfad=$HTTP_SERVER_VARS['DOCUMENT_ROOT']. An dieses Verzeichnis wird

ein Unterverzeichnis angehangen, in das der Webserver hochgeladene Bilder speichert bzw. weitere Unterverzeichnisse erstellt. Dieses Verzeichnis

muss dem Webserver gehören und er muss Schreib-Leserechte besitzen.


Ein Bild aus der DB laden und im Browser ausgeben

In diesem Script habe ich eine kleine Klasse mit ein paar Methoden erstellt, um aus den Bilddaten wieder ein Bild im Browser auszugeben.

Diese Methoden verwenden Funktionen der GD-Bibliothek. Diese Funktionen haben teilweise eine "ellenlange" Parameterliste. Um nicht

immer alle Parameter angeben zu müssen, sind in den Methoden einige (oder alle) Parameter mit Werten vorbelegt. Will man die Werte ändern,

so müssen die Werte von links nach rechts lückenlos eingegeben werden. Beispiel Textfarbe setzen:

/*
 * Methode um Textfarbe zu setzen, einfacher Text
*/
public function set_text_color($r = 255, $g = 0, $b = 0){ 
  $this->text_color = imagecolorallocate ($this->im, $r, $g, $b);
}

Die Standardfarbe für Text ist rot. Würde man nun die Textfarbe auf grün setzen wollen, so notiert man:

$image->set_text_color(0, 255);

Die Ausgabe eines Bilds habe ich in zwei Dateien vorgenommen:

- Eine Datei für die Klasse
- Eine Datei, in der der Datenbankzugriff erfolgt und die Anwendung der Klasse

Die Klassendatei

<?php
class MyImage{
 /*
 * Das Bild
 */
 private $im;
 /*
 * Das Wasserzeichen
 */
 private $copyright_stamp;
 private $stamp_width;
 private $stamp_heigth;
 /*
 * Textparameter für Wasserzeichen
 */
 private $stamp_font_size;
 private $stamp_font_color;
 private $stamp_text;
 private $text_pos_from_left;
 private $text_pos_from_top;
 /*
 * Textfarbe, einfacher Text
 */
 private $text_color;
 /*
 * Konstruktor für das Bild
 */
 public function __construct($str){
  $this->im = imagecreatefromstring(base64_decode($str));
}
 /*
 * Methode um Textfarbe zu setzen, einfacher Text
 */
 public function set_text_color($r = 255, $g = 0, $b = 0){ 
  $this->text_color = imagecolorallocate ($this->im, $r, $g, $b);
 }
 /*
 * Methode um Text einzufügen, einfacher Text
 */
 public function set_text($text, $from_left = 1, $from_top = 30, $font_size = 5){
  if(is_array($text)){
   foreach($text as $textline){
	  ImageString($this->im, $font_size, $from_left, $from_top, $textline, $this->text_color);
		$from_top += 20;
	 }
	}else{
	 			 ImageString($this->im, $font_size, $from_left, $from_top, $text, $this->text_color);
	 			 }
 }
 /*
 * Methoden um ein transparentes Wasserzeichen einzufügen
 *
 * Ein leeres Bild für das Wasserzeichen erstellen
 */
 public function create_copyright_stamp($width, $heigth){
  $this->stamp_width = $width;
  $this->stamp_heigth = $heigth;
  $this->copyright_stamp = imagecreatetruecolor($width, $heigth);
 }
 /*
 * Das Wasserzeichen erstellen, die Form des Wasserzeichens ist rechteckig
 */
 public function set_stamp($border_width = 10, $pos_from_right = 10, $pos_from_bottom = 10, $transparenz = 50, $border_color = 0xff0000, $body_color = 0x000000){
  $x = 0;
  $y = 0;
  imagefilledrectangle($this->copyright_stamp, $x, $y, $this->stamp_width - 1, $this->stamp_heigth - 1, $border_color);
  imagefilledrectangle($this->copyright_stamp, $x + $border_width, $y + $border_width, $this->stamp_width - $border_width, $this->stamp_heigth - $border_width, $body_color);
  if(is_array($this->stamp_text)){
	 foreach($this->stamp_text as $text){
	  imagestring($this->copyright_stamp, $this->stamp_font_size, $this->text_pos_from_left, $this->text_pos_from_top, $text, $this->stamp_font_color);
	  $this->text_pos_from_top += 20;
	 }
	}else{
				imagestring($this->copyright_stamp, $this->stamp_font_size, $this->text_pos_from_left, $this->text_pos_from_top, $this->stamp_text, $this->stamp_font_color);
  			}
	/*
	* Position des Wasserzeichens
	*/
  $marge_right = $pos_from_right;
  $marge_bottom = $pos_from_bottom;
  $sx = imagesx($this->copyright_stamp);
  $sy = imagesy($this->copyright_stamp);
  /*
	* Das Wasserzeichen über das Bild legen
	*/
  imagecopymerge($this->im, $this->copyright_stamp, imagesx($this->im) - $sx - $marge_right, imagesy($this->im) - $sy - $marge_bottom, 0, 0, imagesx($this->copyright_stamp), imagesy($this->copyright_stamp), 

$transparenz);
}
 /*
 * Textparameter für Wasserzeichen setzen
 */ 
 public function set_stamp_text($text, $from_left = 20, $from_top = 30, $font_size = 5, $text_color = 0xff0000){
  $this->stamp_font_size = $font_size;
  $this->stamp_font_color = $text_color;
  $this->stamp_text = $text;
  $this->text_pos_from_left = $from_left;
  $this->text_pos_from_top = $from_top;
 } 
 /*
 * Bild ausgeben und Speicher freigeben
 */
 public function show(){
  imagejpeg($this->im);
	imagedestroy($this->im);  
 }
}
?>

Anwendung der Klasse in einer Datei

Diese Datei stellt unter Verwendung der eingebundenen Klasse das Bild dar. Diese Datei kann im Browser aufgerufen werden, oder man gibt sie

in einer PHP-Datei als Bildresource an: <img src = "mein_bild.php">

<?php
header ("Content-type: image/jpeg");  
include("a_image.inc.php"); //Name der Klassendatei  
error_reporting (E_ALL); 
ini_set ("display_errors", true);    
$conn = new PDO("mysql:host=localhost;dbname=test", "guenni", "guenni");  
$query = "select data from bilder where id = 2";  
$stm = $conn->prepare($query);
$stm->execute();
$result = $stm->fetch(PDO::FETCH_ASSOC);
$image_string = $result['data'];  
/*
* Das Bildobjekt erstellen
*/
$image = new MyImage($image_string);
/*
* Das Bild ausgeben
*/
$image->show();
?>

Damit wäre die Bildausgabe fertig. Um nun noch einen Text als Copyright/transparentes Wasserzeichen über das Bild zu definieren, gibt es zwei Möglichkeiten:

- Eine einzige Textzeile
- Einen mehrzeiligen Text (als Array)

$copyright = "Copyright by ME!";  
$copyright_array = array("Copyright by ME!","(c) 2013","Viersen, NRW, Germany");  

Beide Variablen können der Methode übergeben werden

$image->set_text_color();
$image->set_text($copyright);
//oder mehrzeilig
//$image->set_text($copyright_array);

Das transparente Wasserzeichen

Dazu erstellen wir erstmal ein leeres Bild in der gewünschten Breite und Höhe

$image->create_copyright_stamp(230, 110);

Dann werden die Textparameter für das Wasserzeichen gesetzt

$image->set_stamp_text($copyright);
//oder mehrzeilig
//$image->set_stamp_text($copyright_array);

Anschließend wird das transparente Wasserzeichen erstellt

$image->set_stamp();

Ein Bild mit Text und Wasserzeichen könnte dann so aussehen:

274e92632e5b4461c3e5d333e9bda60c

Abhängig von der Bildgröße muß man mit den Positionsparametern ein wenig ausprobieren, um den Text/das Wasserzeichen im Bild darzustellen.

Zum Schluß noch die Upload-Datei

<?php
error_reporting ( E_ALL ); 
ini_set ( 'display_errors', true );  
/*
* Datenbank Verbindungsparameter
*/
$conn = new PDO("mysql:host=localhost;dbname=test", "guenni", "guenni");  
/*
* Mit $HTTP_SERVER_VARS['DOCUMENT_ROOT'] wird das Rootverzeichnis des Servers ermittelt, 
* pub ist das Standardverzeichnis, in dem Bilder abgelegt werden.
*/
$pfad=$HTTP_SERVER_VARS['DOCUMENT_ROOT']."pub/";  
if(isset($_POST["cmd"])){  
 /*
 * verz wird mit dem Formular gesendet und bestimmt den Ordner,
 * in dem die Datei gespeichert wird.
 * Im Array not_allow können Zeichen(ketten) definiert werden, die im Verzeichnisnamen
 * nicht vorkommen sollen(oder nicht dürfen). Liste der nicht erlaubten Zeichen(ketten (eventuell)) erweitern.
 */
 $not_allow = array(' ','.',':','?',';','www','/','\\');  
 $verz=str_replace($not_allow, '', $_POST['verz']);  
 /*
 * Es wird überprüft, ob $_POST['verz'] existiert. Wenn ja, wird 
 * es dem Pfad angehangen.
 * Wenn nein, wird es erstellt und dem Pfad angehangen.
 * Ist $_POST['verz'] leer, wird die Datei im oben angegeben Ordner gespeichert. 
 */
 if(empty($verz)){
  //keine Aktion
 }else{
 			 if(is_dir($pfad.$verz)){
  		 $pfad.=$verz."/";  
 			 }else{
 			 			 mkdir($pfad.$verz,0777);
 			 			 $pfad.=$verz."/";  
 			 }
			}
 /*
 * Im folgenden werden die Variablen für die copy-Funktion
 * deklariert und belegt.
 */
 $quelle=$_FILES["myfile"]["tmp_name"]; // Name der temporären Datei  
 $quellenname=$_FILES["myfile"]["name"]; // Originalname der Datei  
 $ziel=$pfad.$quellenname; // Originalname wird an Pfad angehangen
 /*
 * Auf Bilddatei prüfen, Liste der Endungen (eventuell) erweitern.
 */
 $bild_end = array(".bmp", ".gif", ".jpg", ".jpeg" , ".png");  
 $punkt_pos = strrpos($quellenname, '.');  
 if(!in_array(strtolower(substr($quellenname, $punkt_pos)), $bild_end)){
  echo "Datei $quellenname ist keine Bilddatei. <a href=\"a_11.php\"> Zurück </a>";  
	exit;
 }
 /*
 * Wird kopiert ?
 */
  if(move_uploaded_file($quelle,$ziel)){
   echo "Datei $quellenname wurde hochgeladen.<br>";   
	 echo "-------------------------------------<br>";  
	 echo "Quelle: ".$quelle."<br>";  
	 echo "Quellenname: ".$quellenname."<br>";  
	 echo "Ziel: ".$ziel."<br>";  
	}else{ /*
				 * if-else copy - Kopieren fehlgeschlagen
				 * Häufiger Grund einer Fehlermeldung ist, dass die hochgeladene Datei größer ist,
				 * als in der php.ini erlaubt ist.
				 */
 			  echo "Fehler beim Hochladen der Datei ".$quellenname."<a href=\"a_11.php\"> Zurück </a>";  
			  exit;
				}
}
/*
* Ist die Checkbox conv angewählt, wird die hochgeladene Datei kodiert und in der DB gespeichert.
*/
if(isset($_POST["conv"])){	  
	$bild = '';  
	$file = $ziel;
	if (!$handle = fopen($file, "r")) die("Konnte Datei $file nicht oeffnen!");    
   while ($str = fread($handle, filesize($file))) {  
    $bild.=base64_encode($str);  
   }
	fclose($handle);
	/*
	* Bild in DB speichern
	*/
  $query = $conn->prepare("insert into bilder(data) values(:bild)");  
	$query->bindParam(":bild", $bild, PDO::PARAM_STR);  
  $result = $query->execute();
	if($result !== false){
	 $id = $conn->lastInsertId();
	 echo "<br>Die ID des eingefügten Datensatzes ist ".$id;  
	}else{
				echo "<pre>";  
				print_r($query->errorInfo());
				echo "</pre>";  
				}
 /*
 * Ist die Checkbox delete angewählt, wird die hochgeladene Datei wieder gelöscht
 */
 if(isset($_POST["delete"])){  
  unlink($file);
 }
}
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">  
<html>
<head>
<title>Upload and Encode</title>
</head>
<body>
<p>Bild hochladen, kodieren und in DB speichern</p>
<form enctype="multipart/form-data" method="post" action="a_11.php">  
<p>Ordner(optional): <input type="text" name="verz"> Bild: <input type="file" name="myfile"></p>  
<input type="submit" name="cmd" value="Hochladen"> Bild kodieren und in DB speichern <input type="checkbox" name="conv">  
Bild nach Upload löschen <input type="checkbox" name="delete">  
</form>
</body>
</html>

Thats all Folks

Gruß
Güni

Content-Key: 219873

Url: https://administrator.de/contentid/219873

Printed on: April 16, 2024 at 19:04 o'clock

Member: LordGurke
LordGurke Nov 01, 2013 at 16:44:35 (UTC)
Goto Top
Hi,

prinzipiell keine schlechte Anleitung, allerdings drei-vier Anmerkungen von mir dazu:

Du benutzt noch $HTTP_SERVER_VARS, was seit Version 4.1 veraltet ist und daher nicht mehr verwendet werden sollte (ich glaube sogar, spätestens unter 5.4 funktioniert es auch tatsächlich nicht mehr). Stattdessen sollte man $_SERVER verwenden: http://php.net/manual/en/reserved.variables.server.php

Dann sollte man bei der Arbeit mit benutzerdefinierten Verzeichnis- und Dateinamen nach Möglichkeit "realpath()" bzw. "basename()" verwenden, um das zu filtern.
Oder direkt einfach eine Whitelist mit den normalen lateinischen Zeichen machen und entsprechend alles, was nicht in der Liste steht zu ersetzen.
Denn dein Filter holt mir z.B. keine Unicode-Zeichen aus dem angegebenen Verzeichnisnamen heraus. Kannst du ja gerne mal mit U+0338 versuchen - sieht aus wie ein Schrägstrich, ist aber keiner: ̸
Oder U+043C, das kyrillische kleine "Em": м

Kann zumindest zu interessanten Konfusionen führen face-wink


Und zum Schluss:
Beim Entwickeln ganz hilfreich, im Produktivsystem aber aus verschiedenen Gründen nicht zu empfehlen:
PHP-Fehler ausgeben, gerade dann wenn man kurz vorher den Content-Type auf "image/jpeg" setzt.
Du erfährst alle PHP-Fehler und Warnungen auch aus dem Error-Log deines Webservers. Wobei... Mit E~ALL hättest du auch eigentlich Deprecated-Warnungen sehen müssen?
Member: Guenni
Guenni Nov 02, 2013 at 04:43:37 (UTC)
Goto Top
Zitat von @LordGurke:
Hi,

prinzipiell keine schlechte Anleitung, allerdings drei-vier Anmerkungen von mir dazu:

Du benutzt noch $HTTP_SERVER_VARS, was seit Version 4.1 veraltet ist und daher nicht mehr verwendet werden sollte (ich glaube
sogar, spätestens unter 5.4 funktioniert es auch tatsächlich nicht mehr). Stattdessen sollte man $_SERVER verwenden:
http://php.net/manual/en/reserved.variables.server.php

Dann sollte man bei der Arbeit mit benutzerdefinierten Verzeichnis- und Dateinamen nach Möglichkeit "realpath()"
bzw. "basename()" verwenden, um das zu filtern.
Oder direkt einfach eine Whitelist mit den normalen lateinischen Zeichen machen und entsprechend alles, was nicht in der Liste
steht zu ersetzen.
Denn dein Filter holt mir z.B. keine Unicode-Zeichen aus dem angegebenen Verzeichnisnamen heraus. Kannst du ja gerne mal mit
U+0338 versuchen - sieht aus wie ein Schrägstrich, ist aber keiner: ̸
Oder U+043C, das kyrillische kleine "Em": м

Kann zumindest zu interessanten Konfusionen führen face-wink


Und zum Schluss:
Beim Entwickeln ganz hilfreich, im Produktivsystem aber aus verschiedenen Gründen nicht zu empfehlen:
PHP-Fehler ausgeben, gerade dann wenn man kurz vorher den Content-Type auf "image/jpeg" setzt.
Du erfährst alle PHP-Fehler und Warnungen auch aus dem Error-Log deines Webservers. Wobei... Mit E~ALL hättest du auch
eigentlich Deprecated-Warnungen sehen müssen?


Hi,

danke für die Rückmeldung. Zu $HTTP_SERVER_VARS: Da sieht man mal, wie alt das Uploadscript ist. Ich bin auf die Idee,

eine Anleitung zu schreiben, erst gekommen, nach dem ein Forenuser (ich weiß nicht mehr wo) geschrieben hatte, dass er Probleme

hätte, ein Bild aus einer Datenbank im Browser auszugeben. Deshalb wurde das Script einfach nur erweitert.

Was du mit realpath oder basename "filtern" willst, erschließt sich mir jetzt nicht so direkt. realpath macht aus

../../ einen absoluten Pfad, den ich ja auch angebe, und basename gibt aus Pfad/Pfad/Dateiname den Dateinamen zurück.

Wenn du mit Filtern das hier meinst . . .

$not_allow = array(' ','.',':','?',';','www','/','\\');   
$verz=str_replace($not_allow, '', $_POST['verz']);   

. . . so steht es jedem frei

- die Liste der Zeichen zu erweitern
- die Liste der Zeichen zu reduzieren
- die beiden Zeilen auszukommentieren
- sich selber einen Filter zu basteln.

Auf Copyright lege ich keinerlei Wert, jeder kann was ich schreibe benutzen, umändern, erweitern . . . wie er lustig ist.

Ich wollte einfach nur eine Möglichkeit zeigen, sich unerwünschte Zeichen in Verzeichnisnamen "vom Hals" (Server) zu halten.

Zu Fehler, Warnungen und Deprecated-Warnungen: Ist die Bildausgabe erstmal gestartet, verschwinden diese im Nirwana, weil ja ein

Bild ausgegeben wird. Dazu müsste man ein Extrabild erstellen und Fehler und Warnungen dort ausgeben.

Gruß
Günni