PHP & MySQL

Un watermak PNG propre en PHP

Le 14 décembre 2007 à 12:04 par Séverin

Le watermark (filigrane), consiste à rajouter une image sur une autre. Elle va par exemple servir à signaler de quel site provient une image.

Exemple de filigrane

Il existe déjà des fonctions de gestion des images en PHP dont imagecopymerge qui permet de faire le collage rapidement mais qui gère très mal la transparence des fichiers PNG.

Pour faire un collage propre il va donc falloir le faire soit même. Voilà donc l’explication pas à pas.

Création d’une classe

La première étape pour ce genre de code est de créer une version réutilisable et de permettre l’ajout d’options. Pour ça, on va créer une classe Watermarker.

Les options par défaut d’une telle classe seront le chemin vers le fichier tampon (l’image à ajouter en filigrane), la position de ce tampon et enfin son opacité.

  1.  
  2. define(‘POSITION_MIDDLE’, 0);
  3. define(‘POSITION_TOP’, -1);
  4. define(‘POSITION_BOTTOM’, 1);
  5. define(‘POSITION_LEFT’, -1);
  6. define(‘POSITION_RIGHT’, 1);
  7.  
  8. class Watermarker{
  9.  
  10.     var $stampPath = null;
  11.     var $vAlign = POSITION_LEFT;
  12.     var $hAlign = POSITION_TOP;
  13.     var $alpha = 100;
  14.  
  15.     function Watermarker($stampPath, $vAlign=POSITION_TOP, $hAlign=POSITION_LEFT, $alpha=100){
  16.         $this->stampPath = $stampPath;
  17.         $this->vAlign = $vAlign;
  18.         $this->hAlign = $hAlign;
  19.         $this->alpha = $alpha/100;
  20.     }
  21.  
  22.     …
  23.  
  24. }
  25.  

Ouverture des images

Tout d’abord on va ouvrir les images. Mais comme on ne sait pas quel est le format de l’image, on va commencer par une petite fonction chargée de le détecter :

  1.  
  2. function openImage($path){
  3.     // On détecte l’extension
  4.     $ext = strtolower(substr($path, strrpos($path, ‘.’) + 1));
  5.  
  6.     // On test les différnetes extensions lisibles
  7.     switch($ext){
  8.         case ‘png’ :
  9.             return imagecreatefrompng($path);
  10.             break;
  11.         case ‘gif’ :
  12.             return imagecreatefromgif($path);
  13.             break;
  14.         case ‘jpg’ :
  15.         case ‘jpeg’ :
  16.             return imagecreatefromjpeg($path);
  17.             break;
  18.         default :
  19.             return false;
  20.     }
  21. }
  22.  

Ajout du tampon

Maintenant on va pouvoir entrer dans le vif du sujet, la création de l’image marquée.

On commence par ouvrir les images

  1.  
  2. if(!($image = $this->openImage($imagePath))){
  3.     return false;
  4. }
  5. if(!($stamp = $this->openImage($this->stampPath)){
  6.     return false;
  7. }
  8. $output = imagecreatetruecolor($imageWidth, $imageHeight);
  9.  

On calcul la position du filigrane

  1.  
  2. // Dimension des images
  3. $imageWidth     = imagesx($image);
  4. $imageHeight    = imagesy($image);
  5. $stampWidth     = imagesx($stamp);
  6. $stampHeight    = imagesy($stamp);
  7.  
  8. // Calcul de la position
  9. $stampX = 0;
  10. if($this->hAlign==POSITION_MIDDLE){
  11.     $stampX = floor(($imageWidth/2)-($stampWidth/2));
  12. }elseif($this->hAlign==POSITION_RIGHT){
  13.     $stampX = $imageWidth-$stampWidth;
  14. }
  15.  
  16. $stampY = 0;
  17. if($this->vAlign==POSITION_MIDDLE){
  18.     $stampY = floor(($imageHeight/2)-($stampHeight/2));
  19. }elseif($this->vAlign==POSITION_BOTTOM){
  20.     $stampY = $imageHeight-$stampHeight;
  21. }
  22.  

Et enfin on parcourt l’image de fond pixel par pixel pour dessiner l’image de sortie pixel par pixel…

  1.  
  2. for($y=0; $y<$imageHeight; $y++){
  3.     for($x=0; $x<$imageWidth; $x++){
  4.         …
  5.     }
  6. }
  7.  

Lorsqu’on parcours des pixels se trouvant dans la zone du filigrane, on calcul le mélange de couleur :

  1.  
  2. if($x>=$stampX && $x<=$stampX+$stampWidth && $y>=$stampY && $y<=$stampY+$stampHeight){
  3.     …
  4. }
  5.  

Pour cela, on récupère les données de couleur et de transparence de chaque pixel :

  1.  
  2. // Calcul de la poisition correspondante sur le tampon
  3. $x2 = $x - $stampX;
  4. $y2 = $y - $stampY;
  5.  
  6. // On récupère la couleur du pixel courant sur l’image
  7. $RGB = imagecolorsforindex($image, imagecolorat($image, $x, $y));
  8. // On récupère la couleur du pixel courant sur le tampon
  9. $stampRGB = imagecolorsforindex($stamp, imagecolorat($stamp, $x2, $y2));
  10.  

Cette fonction retourne un tableau avec les index red, green, blue et alpha.

Pour rendre la donnée alpha utilisable, il suffit de lui appliquer le calcul suivant :
alpha = ((127 - alpha) / 127) X alpha global

  1.  
  2. $stampAlpha = round(((127-$stampRGB[‘alpha’])/127), 2 )* $this->alpha;
  3.  

Maintenant, on peut calculer la valeur du pixel de l’image marquée pour chaque composante RGB :
Couleur de sortie = (couleur du fond X inverse de l’alpha) + (couleur de tampon X alpha)

  1.  
  2. $RGB[‘red’] = round((($RGB[‘red’]*(1-$stampAlpha))+($stampRGB[‘red’]*$stampAlpha)));
  3. $RGB[‘green’] = round((($RGB[‘green’]*(1-$stampAlpha))+($stampRGB[‘green’]*$stampAlpha)));
  4. $RGB[‘blue’] = round((($RGB[‘blue’]*(1-$stampAlpha))+($stampRGB[‘blue’]*$stampAlpha)));
  5.  

On retransforme alors en couleur utilisable en sortie :

  1.  
  2. $RGB = imagecolorexact($image, $RGB[‘red’], $RGB[‘green’], $RGB[‘blue’]);
  3. if ($RGB<0){
  4.     $RGB = imagecolorallocate($image, $RGB[‘red’], $RGB[‘green’], $RGB[‘blue’]);
  5. }
  6. if ($RGB<0){
  7.     $RGB = imagecolorclosest($image, $RGB[‘red’], $RGB[‘green’], $RGB[‘blue’]);
  8. }
  9.  

On dessine le pixel :

  1.  
  2. imagesetpixel($output, $x, $y, $RGB);
  3.  

Et une fois tout les pixels dessinés, on ferme les fichiers en entré et on retourne l’image de sortie. La fonction d’une traite donne donc :

  1.  
  2. function makeOutput($imagePath){
  3.     // Ouverture des images
  4.     if(!($image = $this->openImage($imagePath))){
  5.         return false;
  6.     }
  7.     if(!($stamp = $this->openImage($this->stampPath)){
  8.         return false;
  9.     }
  10.  
  11.     // Dimension des images
  12.     $imageWidth     = imagesx($image);
  13.     $imageHeight    = imagesy($image);
  14.     $stampWidth     = imagesx($stamp);
  15.     $stampHeight    = imagesy($stamp);
  16.  
  17.     // Calcul de la position
  18.     $stampX = 0;
  19.     if($this->hAlign==POSITION_MIDDLE){
  20.         $stampX = floor(($imageWidth/2)-($stampWidth/2));
  21.     }elseif($this->hAlign==POSITION_RIGHT){
  22.         $stampX = $imageWidth-$stampWidth;
  23.     }
  24.  
  25.     $stampY = 0;
  26.     if($this->vAlign==POSITION_MIDDLE){
  27.         $stampY = floor(($imageHeight/2)-($stampHeight/2));
  28.     }elseif($this->vAlign==POSITION_BOTTOM){
  29.         $stampY = $imageHeight-$stampHeight;
  30.     }
  31.  
  32.     // On crée l’image de sortie
  33.     $output = imagecreatetruecolor($imageWidth, $imageHeight);
  34.  
  35.     //On parcours les pixels de l’image principale
  36.     for($y=0; $y<$imageHeight; $y++){
  37.         for($x=0; $x<$imageWidth; $x++){
  38.             //$imageColor = NULL;
  39.  
  40.  
  41.             $RGB = imagecolorat($image, $x, $y);
  42.  
  43.             // Si on est dans la zone du tampon
  44.             if($x>=$stampX && $x<=$stampX+$stampWidth && $y>=$stampY && $y<=$stampY+$stampHeight){
  45.  
  46.                 // Calcul de la poisition correspondante sur le tampon
  47.                 $x2 = $x - $stampX;
  48.                 $y2 = $y - $stampY;
  49.  
  50.                 // On récupère la couleur du pixel courant sur l’image
  51.                 $RGB = imagecolorsforindex($image, $RGB);
  52.                 // On récupère la couleur du pixel courant sur le tampon
  53.                 $stampRGB = imagecolorsforindex($stamp, imagecolorat($stamp, $x2, $y2));
  54.  
  55.                 // Lecture des données de transparence
  56.                 $stampAlpha = round(((127-$stampRGB[‘alpha’])/127), 2 )* $this->alpha;
  57.  
  58.                 // Mélange de couleur de pixels de l’image et du tampon
  59.                 $RGB[‘red’] = round((($RGB[‘red’]*(1-$stampAlpha))+($stampRGB[‘red’]*$stampAlpha)));
  60.                 $RGB[‘green’] = round((($RGB[‘green’]*(1-$stampAlpha))+($stampRGB[‘green’]*$stampAlpha)));
  61.                 $RGB[‘blue’] = round((($RGB[‘blue’]*(1-$stampAlpha))+($stampRGB[‘blue’]*$stampAlpha)));
  62.  
  63.                 $RGB = imagecolorexact($image, $RGB[‘red’], $RGB[‘green’], $RGB[‘blue’]);
  64.                 if ($RGB<0){
  65.                     $RGB = imagecolorallocate($image, $RGB[‘red’], $RGB[‘green’], $RGB[‘blue’]);
  66.                 }
  67.                 if ($RGB<0){
  68.                     $RGB = imagecolorclosest($image, $RGB[‘red’], $RGB[‘green’], $RGB[‘blue’]);
  69.                 }
  70.             }
  71.  
  72.             // Dessine le pixel
  73.             imagesetpixel($output, $x, $y, $RGB);
  74.         }
  75.     }
  76.  
  77.     imagedestroy($image);
  78.     imagedestroy($stamp);
  79.  
  80.     return $output;
  81. }
  82.  

Gestion du cache

Cette fonction peut être assez lourde, il est donc important de gérer le cache et de ne pas recalculer à chaque fois l’image.

Pour ça, notre classe dispose de deux fonctions, une pour afficher l’image et une pour l’enregistrer.

L’enregistrement est simple :

  1.  
  2. function save($imagePath, $outputPath, $quality=75) {
  3.     if($output = $this->makeOutput($imagePath)){
  4.         imagejpeg($output, $outputPath, $quality);
  5.         return true;
  6.     }
  7.     return false;
  8. }
  9.  

L’affichage s’occupe donc de détecter le cache

  1.  
  2. function display($imagePath, $quality=75, $cachePath = null) {
  3.     if($cachePath){
  4.         $cacheName = ‘/’.md5($imagePath).‘.jpg’;
  5.         if(!file_exists($cachePath.$cacheName)){
  6.             if(!$this->save($imagePath, $cachePath.$cacheName, $quality)){
  7.                 return false;
  8.             }
  9.         }
  10.         $display = imagecreatefromjpeg($cachePath.$cacheName);
  11.         header("content-type: image/jpeg");
  12.         imagejpeg($display);
  13.     }else{
  14.         header("content-type: image/jpeg");
  15.         imagejpeg($this->makeOutput($imagePath));
  16.     }
  17. }
  18.  

Utilisation

Maintenant, il ne reste plus qu’à voir comment la classe s’utilise :

  1.  
  2. $cachePath = ‘/images/watermarker.cache’;
  3. $imagePath = ‘/images/photo/arbre.jpg’;
  4. $logoPath = ‘/images/logo.png’;
  5.  
  6. $watermarker = new Watermarker($logoPath, POSITION_BOTTOM, POSITION_RIGHT, 75);
  7. $watermarker->display($imagePath, 75, $cachePath);
  8.  

Bon, je vais pas vous laissez faire des copier coller fastidieux, voilà la classe prête à utiliser : Classe de Watermark

13 commentaires »

Gravatar

Pingback de Smashing Coding » Ajouter automatiquement son logo sur les images volées

le 14 décembre 2007 à 12:59

[…] Pour cette fonction, on va utiliser le Watermarker d’un précédent article. […]

Gravatar

Commentaire de Charliend

le 14 décembre 2007 à 15:44

Très bon tuto. Merci beaucoup!

Gravatar

Commentaire de Pierre

le 17 décembre 2007 à 15:56

Il serait bien de savoir de quoi on parle avant de donner de mauvais conseil.

Imagecopymerge est la pour simuler le channel alpha via le facteur “pct”, comme tt le monde peut le lire dans la doc PHP:

http://www.php.net/imagecopymerge

imagecopy par exemple te permettra de copier une image provenant d’un fichier et d’utiliser son channel alpha (niveau de transparence). Tu trouveras un exemple avec imagecopy et imagecopymerge dans cette page:

http://pierre.libgd.org/watermark/

J’ai essayé de rester simple afin de ne pas ajouter encore plus de confusions :).

Si tu as des questions, n’hesite pas a me contacter :)

Gravatar

Trackback de www.blogmemes.fr

le 17 décembre 2007 à 18:32

Un watermak PNG propre en PHP…

- Vous aimez cet article ? Votez pour lui sur Blogmemes.fr !Le watermark (filigrane), consiste à rajouter une image sur une autre. Elle va par exemple servir à signaler de quel site provient une image.
Pour faire un collage propre il va falloir le…

Gravatar

Commentaire de Séverin

le 18 décembre 2007 à 10:41

@pierre :

C’est vrai que j’ai pas été très clair sur l’introduction.

Quand je parle de filigrane, je parle de la transparence du PNG mais aussi d’une transparence globale.

imageCopy ne gère pas la transparence globale et imagecopymerge (plus quelques manipulations) arrive à la gérer mais pas sur toutes les images.

Le Watermarker que je propose gère donc les deux à la fois : la transparence du fichier PNG et la transparence globale définie et il gère également le cache.

Gravatar

Commentaire de Pierre

le 18 décembre 2007 à 11:19

“Quand je parle de filigrane, je parle de la transparence du PNG mais aussi d’une transparence globale.”

La couleur transparente (ou background) est une propriete des images basees sur des palettes.

Le format PNG par exemple ne peut permet pas d’avoir des couleurs translucides (alpha non opaque) et une couleur transparente.

“imageCopy ne gère pas la transparence globale et imagecopymerge (plus quelques manipulations) arrive à la gérer mais pas sur toutes les images.”

Par exemple? Une image en memoire n’a plus rien a voir avec le format du fichier. Elle est soit au format truecolor ou utilise une palette de couleurs.

“Le Watermarker que je propose gère donc les deux à la fois : la transparence du fichier PNG et la transparence globale définie et il gère également le cache.”

Je ne dis pas que la classe est inutile, seulement que l’operation de blending ne devrait pas etre faite a la main. Les fonctions imagecopy et copymerge fonctionnent tres bien les deux types d’images. Si tu as des exemples demontrant des bugs et/ou des problemes, je serais tres heureux de les analyser et de les fixer le cas echeant.

Gravatar

Commentaire de Séverin

le 18 décembre 2007 à 11:49

L’exemple simple, je veux mon logo, avec sa transparence PNG (l’antialiasing du texte par exemple) mais que son fond habituellement opaque ne soit qu’à 50% d’opacité (et donc l’antialiasing que à 50% de son opacité normale).

Si imageCopyMerge propose bien un paramètre de correction d’opacité (le dernier), imageCopy n’en propose pas.

Voilà les différenets résultats qu’on peut obtenir avec les différentes méthodes.

http://www.gamesandgeeks.com/images/demowatermark.htm

Plutôt que le mixage pixel par pixel, une autre méthode aurait été de transformer l’image du logo en en réduisant l’alpha global avant de faire un imagecopy. Mais j’ai préféré le traitement pixel par pixel car ça ouvre plus de possibilités derrière même si elles sont pas utilisées ici.

Gravatar

Commentaire de Pierre

le 18 décembre 2007 à 12:00

“L’exemple simple, je veux mon logo, avec sa transparence PNG (l’antialiasing du texte par exemple) mais que son fond habituellement opaque ne soit qu’à 50% d’opacité (et donc l’antialiasing que à 50% de son opacité normale).”

Il ne faut pas melanger un antialiasing normal qui n’utilise pas le channel alpha (le blending est fait contre la couleur de fond) et un antialiasing utilisant le channel alpha.

En regardant tes examples, ce que tu aimerais est un mix de imagecopy et de imagecopymerge. C’est a dire d’utiliser le channel alpha de l’image ET ajouter (ou soustraire/diviser/multiplier/…) une valeur a celui-ci. Ce n’est pas directement possible. As-tu l’image utilisee pour le stampe?

Par contre, et cela peut etre plus rapide, tu peux le faire en deux operations:

1. modifie l’alpha du stamp avec imagefilter et le filtre “colorize”, ajoute 50% alpga:
$im = imagefilter(IMG_FILTER_COLORIZE, 0, 0, 0, 63);

2. copie le stamp

J’espere que ca va ameliorer ta classe :)

Gravatar

Commentaire de Séverin

le 18 décembre 2007 à 12:19

Comme je l’ai précisé dans mon commentaire, il est en effet possible de faire comme tu le décrit, en modifiant la transparence du tampon avant imagecopy.

Mais je vise d’ajouter d’autres capacités à la classe dont l’alpha dégradé pour apparaitre plus sur les bords que au milieu de l’image pour ne pas gêner.

Donc, oui, il y’a d’autres façon de faire, mais celle-ci en est une viable. Même si elle est un peu plus gourmande elle a aussi ces avantages.

De plus, pour un tutoriel il est intéressant de voir comment on peut traiter une image au niveau du pixel.

Gravatar

Commentaire de Pierre

le 18 décembre 2007 à 12:40

“De plus, pour un tutoriel il est intéressant de voir comment on peut traiter une image au niveau du pixel.”

C’est clair, mais c’est a eviter dans la mesure du possible. Les appels de fonctions php sont horriblement lents (comme ds tous languages de script). L’avantage de la foncton imagefilter est que l’iteration sur tous les pixels et tres rapide.

Gravatar

Commentaire de Séverin

le 18 décembre 2007 à 12:54

Oui, dans la mesure du possible, il faut utiliser les fonctions PHP qui sont compilées plutôt que le script qui est interprété à la volée.

Gravatar

Commentaire de chapodepay

le 28 janvier 2008 à 22:30

Il y a des soucis dans le fichier watermark…
genre parenthese oublié ligne 47…
j’espere que c’est le seul souci car je galère a le faire tourner la !

merci tout de meme

Gravatar

Pingback de Immagini con watermark con php : sastgroup.com

le 9 avril 2008 à 14:55

[…] l’articolo: http://smashingcoding.com/2007/12/14/un-watermak-png-propre-en-php/ Share and Enjoy: These icons link to social bookmarking sites where readers can share and […]

Laisser un commentaire

Votre Nom

Votre E-mail (obligatoire mais ne sera pas publié)

Votre Site ou blog

Votre commentaire

Valid XHTML 1.0 Transitional