Il y a 5 ans, j'ai eu un iPod Touch de première génération. C'était tout nouveau, tout beau. Au début, c'était le fun et j'avais l'impression que l'appareil offrait un potentiel intéressant. J'ai fait quelques expérimentations puis, je m'en suis vite désintéressé. À la maison, mon jukebox touchscreen me suffisait et au bureau, je trainais mon disque dur WD Passport pour avoir toute ma librairie sous la main. Il a donc sombré dans l'oubli pendant la dernière année, peut-être même deux ans.
Récemment, suite à mon inscription à TrueFire, j'ai remarqué que leur application Guitar Lab était gratuite et compatible. J'ai alors ressorti mon petit gadget des boules à mites en me disant que je pourrais enfin lui trouver une application pratique. En fouillant dans la catégorie Musique du App Store, j'en ai trouvé quelque unes qui suscitaient ma curiosité. J'ai bien tenté d'en installer une bonne douzaine mais sans succès. Juste parce que la version maximum supportée du OS est 3.1.3 et que la plupart sont développées pour les appareils plus récents. J'en suis venu à la conclusion que le mien était désuet.
Sauf exception, j'ai pu installer une autre application pour aider le guitariste à s'entraîner à reconnaître rapidement les notes sur le manche de guitare. Sous forme de jeu, il doit obtenir le plus de bonnes réponses dans un temps donné. Mon record personnel est de 68 en 2 minutes.
Fretboard Warrior est disponible gratuitement dans sa version Lite et une version à faible coût offre plus d'options pour se concentrer sur la portion du fretboard qui nous donne le plus de difficultés. L'application est très bien faite, même si elle n'a pas été retouchée depuis 2009 par son développeur (François Brisson, de Montréal). Pour encore plus d'options, il faudra se tourner vers Fret Surfer Guitar Trainer (2,99$). Sans surprise, celle-là n'est pas compatible sur mon iPod...
Zend_Date me fait perdre mon temps avec l'heure d'été
Il n'y a pas si longtemps, j'expliquais comment Zend_Date provoquait des sauts étranges dans le temps. J'ai expliqué la nature du problème et comment le corriger. Toujours avec le même composant (Zend Framework, version 1.10.8) mais dans un autre contexte, un client s'est plaint que l'heure faisait un bond d'une heure lorsqu'il en sélectionnait une le 11 mars 2012. Mon intuition : un conflit avec l'heure d'été...
Après vérification, passage à l’heure d’été au Québec a lieu le deuxième dimanche du mois de mars, à 2 heures du matin (heure locale). Ça correspond effectivement au 11 mars.
Pour constater le problème, j'ai isolé le code comme ceci :
require_once('Zend/Loader/Autoloader.php');Pourtant...
$autoloader = Zend_Loader_Autoloader::getInstance();
$locale = new Zend_Locale('fr_ca');
Zend_Registry::set('Zend_Locale', $locale);
// OK
$date = new Zend_Date('2012-03-11 08:00:00');
echo $date->toString('yyyy-MM-dd HH:mm:ss');
// 2012-03-11 08:00:00
$date = new Zend_Date('2012-03-11');En définissant la date et l'heure séparément, l'impression indique 9h.
$date->setTime('08:00:00');
echo $date->toString('yyyy-MM-dd HH:mm:ss');
// 2012-03-11 09:00:00
J'ai écrit une boucle pour voir où ça se passe.
$date = new Zend_Date('2012-03-11');Si c'est un problème de DST (Daylight Saving Time), le retour à l'heure normale présenterait le même problème. Au Québec, le retour à l’heure d’hiver a lieu le premier dimanche du mois de novembre à 2 heures du matin (heure locale), soit le 4 novembre.
$date->setTime('00:00:00');
for($i=0 ; $i<=6 ; $i++){
echo $date->toString('yyyy-MM-dd HH:mm:ss');
$date->addHour(1);
}
/*
2012-03-11 00:00:00
2012-03-11 01:00:00
2012-03-11 03:00:00 // 2h AM est disparu
2012-03-11 04:00:00
2012-03-11 05:00:00
2012-03-11 06:00:00
2012-03-11 07:00:00
*/
Constatons cela.
$date = new Zend_Date('2012-11-04');Comment solutionner ce problème ? Malheureusement, c'est un bogue connu et documenté chez Zend et qui ne semble toujours pas réglé. Certaines personnes prétendent qu'il faut désactiver le DST en modifiant le timezone à GMT0 :
$date->setTime('00:00:00');
for($i=0 ; $i<=12 ; $i++){
echo $date->toString('yyyy-MM-dd HH:mm:ss');
$date->addHour(1);
}
/*
2012-11-04 00:00:00
2012-11-04 01:00:00
2012-11-04 01:00:00 // deux fois 1h AM
2012-11-04 02:00:00
2012-11-04 03:00:00
2012-11-04 04:00:00
...
*/
date_default_timezone_set('GMT0');Effectivement, tous les tests précédents ne montrent plus les sauts. Mais un simple affichage de date('Y-m-d h:i:s') montre aussi que le jour et l'heure ne représentent plus la situation du Québec (il est présentement environ 21h et ça indique le lendemain à 2h du matin). À ne pas retenir.
Sur StackOverflow, un utilisateur suggère un workaround en effectuant deux appels de suite à setTime() pour forcer la fonction à vérifier si le DST a changé. Je confirme que ça fonctionne bien et qu'à défaut d'avoir une solution plus élégante, c'est elle que j'ai mis en place dans le projet. Accompagnée d'un commentaire sympathique dans le code pour expliquer la situation au prochain programmeur qui passera par là.
Honte à moi, je viens de me faire cuire une Pizza Pochette de McCain. Ce n'est pas de la grosse gastronomie mais ça bouche un coin en attendant le repas, surtout que Canadiens vient d'amorcer un match plus tôt qu'à l'habitude. Alors je grignotte avant le souper (qui risque d'être une salade).
Sur l'emballage, j'ai été surpris de voir dans le coin un argument marketing inhabituel : Aucun ingrédient imprononçable. Pfff, pourquoi pas !
C'est encore inexplicable pour moi comment un petit livre du nom de If The Shoe Fits s'est retrouvé sur ma table de salon durant la nuit. À croire que quelqu'un l'a laissé là en espérant m'éduquer sur quelques faits amusants sur les souliers. Toujours est-il que j'ai fini par le feuilleter et j'ai découvert que l'homme le plus grand du monde de tous les temps portait une pointure de 44. Robert Wadlow, originaire de Alton en Illinois, mesurait rien de moins que 2,72 mètres (8 pieds et 11 pouces). Assez pour que le géant Zdeno Chara (qui ne fait que 6'9") se sente comme Ping Ping qui se cache dans ses souliers.
Ça m'a intéressé à fouiller sur la démesure qu'on retrouve au pays de l'oncle Sam. J'ai découvert que non loin de là, à 40 km au sud, se trouve Collinsville. Qu'est-ce qu'on y retrouve de si exceptionnel ? Rien. Sauf un monument extraordinaire.
Imaginez un instant un spot publicitaire : Vous êtes en vacances en famille en Illinois. Les enfants s'ennuient et vous manquez d'inspiration pour les divertir ? Ne manquez pas de faire un détour par Collinsville pour admirer la plus grosse bouteille de ketchup (ou catsup ?) de marque Brooks qui trône au milieu d'un décor bucolique.
Au programme :
- participation au festival annuel (mois de juillet)
- séance photo-souvenir et inscription au fanclub officiel (et supportez la cause en devenant fan de la page Facebook)
- rencontre avec le Catsup Bottle Preservation Group qui vous racontera comment ils ont su sauver l'attraction de la démolition et vous expliquera en détails les techniques utilisées pour restaurer la bouteille à son apparence d'origine pour seulement 77000$
- profitez-en pour inscrire vos enfants au concours pour être nommés "Little Princess Tomato" et "Sir Catsup"
- achetez en souvenir le t-shirt officiel (si votre taille est disponible)
- sinon, virée de magasinage chez Buff Truck Outfitters juste en face
Adresse du monument historique pour votre itinéraire (à voir par StreetView) :
(oui, il est inscrit au National Register of Historic Places)
800 South Morrison Avenue, Collinsville, Illinois
Enfin, on ne pourrait pas clore le sujet sans passer sous silence ces deux excellentes publicités québécoises des années 80 pour le ketchup Heinz. Célibataires, prenez note des astuces.
Ça n'a pas d'importance d'être le meilleur guitariste au monde, si vous n'êtes pas éclairé, laissez tomber.
Voici le personnage le plus vulgaire de tous les temps : il se censure à chaque mot qu'il dit.
Merci à Dan d'avoir partagé cette trouvaille. C'est une *BIP* de bonne joke geek :)
Durant une conversation sur l'heure du lunch, l'étudiant que nous embauchons en a profité pour faire du name dropping d'un design pattern qu'il a appris récemment durant un de ses cours. Manifestement, il était un peu trop enthousiaste à expliquer qu'il a pu faire des expérimentations à ce sujet dans une portion d'un de nos projets. L'équation "étudiant + expérimentation + notion non-maitrisée" a déclenché un stack overflow dans ma tête. Mieux valait intervenir tout de suite sans quoi, c'est encore bibi qui devra réajuster le tout dans un avenir plus ou moins rapproché. Immédiatement, je l'ai interrompu en lui faisant remarquer que non seulement il utilisait le pattern dans un mauvais contexte, mais surtout qu'il valait mieux ne pas essayer d'en appliquer juste pour le plaisir, que le choix d'en utiliser un doit d'abord solutionner un problème.
Tous les jours, on crée des patterns sans s'en rendre compte. Ce n'est qu'en analysant le travail effectué qu'on peut les mettre en évidence et si nécessaire, les nommer. Certains semblent ne jurer que par la bible du GoF qu'ils élèvent au niveau de livre culte. Loin de vouloir dénigrer ce livre (que je possède), je reproche à ses adeptes le manque de jugement quant à la façon de les mettre en application. Des concepts solides mais souvent fort mal utilisés.
Pour bien les comprendre et les apprécier, il faut parfois s'être planté en optant pour une solution moins élégante et plus compliquée à implémenter (menant parfois au blasphème et des nuits blanches). Les patterns ne sont qu'une solution parmi tant d'autres et il en existe beaucoup plus qu'il y en a de documentés.
Le terme design patterns a magnifiquement été traduit par patrons de conception. Un patron, ça m'évoque un modèle utilisé pour confectionner des vêtements. Pour que ce modèle puisse exister, il y avait d'abord un prototype unique, complété à force d'essais et d'erreurs, qu'on souhaitait ensuite imiter et reproduire en contournant les obstacles rencontrés lors de la création originale. Quand on écrit du code sur mesure, il n'y a pas que les tailles Small, Medium, Large et surtout pas One Size Fits All. Même pour les patterns, on doit y apporter différentes altérations pour que ça s'imbrique à la perfection dans notre projet. Ce n'est jamais une solution définitive mais plutôt une piste à suivre en vue de la résolution d'un problème.
C'est une source d'inspiration pour créer sa propre recette. Bien sûr, on pourrait suivre les étapes à la lettre en mélangeant les ingrédients mesurés avec exactitude mais il y a parfois des contraintes qui nous forcent à adapter la formule à nos besoins. Après tout, il n'existe pas qu'une seule recette de sauce à spaghetti même si la base de sauce tomate représente 75% de la solution. Tel un chef de cuisine, on ajoute de la personnalité au 25% restant. Créativité. Inventivité.
Enfin, la meilleure analogie qu'on m'a donné sur les patrons de conception est de faire le lien avec une gamme musicale. Même si on connaît toutes les notes de la gamme, qu'on les joue toutes dans le même ordre et au même rythme, ça ne produira pas une musique agréable. On ne peut pas ouvrir un livre de référence sur les gammes, en choisir une, la jouer sur son instrument et espérer que le public l'appréciera. Au contraire, le résultat sera ennuyant et n'atteindra pas son objectif. Idem pour les Design Patterns. Il faut voir ce que propose le GoF comme une référence sur les gammes : un point de départ solide pour construire des solos mais c'est à nous de les appliquer et les adapter pour en faire de la bonne musique.
Convertir une image couleur à grayscale avec le canvas HTML5
Comme la plupart des gens, vous avez certainement vu le film Les aventures de Tintin en 3D qui raconte l'histoire d'un jeune reporter roux d'à peu près 17 ans qui sait piloter une moto comme un champion et un avion en situation catastrophique. Moi, je ne l'ai vu que récemment avant qu'il disparaisse des salles de cinéma (avec les lunettes BCBG). Ce qui m'a le plus impressionné, c'est l'hallucinante qualité de l'image 3D et les détails plus vrais que nature reproduits par ordinateur. Bientôt, l'ère des vrais acteurs payés à coup de millions sera révolue et constituera un genre à part dans l'industrie. Il n'y a qu'à imaginer quel cachet aurait demandé Pauline Marois pour jouer le rôle secondaire de la Castafiore pour comprendre l'étendue du génie économique de Spielberg et Jackson.
Tout ça pour dire que le film avait un facteur wow gigantesque, du jamais vu. Comme programmeur, nul besoin de dire que je suis incapable de produire une affiche avec une palette de couleurs si éblouissante. Par contre, mes démarches d'autoformation avec le canvas HTML5 et la manipulation d'images m'ont intéressé à voir comment on pouvait convertir une image couleur en niveaux de gris (grayscale). C'est moins impressionnant mais l'effet sobre fait ressortir les jeux de lumière, comme dans une photo en noir et blanc.
Voici le code final, je vous explique les nouvelles notions ensuite.
var canvas = $('#myCanvas');
var context = canvas.get(0).getContext('2d');
var img = new Image();
img.src = 'images/tintin-3d.jpg';
$(img).load(
function(){
context.drawImage(img, 0, 0);
var imageData = context.getImageData(0, 0, canvas.width(), canvas.height())
var pixels = imageData.data;
var nbPixels = pixels.length;
for(var i=0 ; i<nbPixels ; i++){
// moyenne RGB pour obtenir le ton de gris
var average = (pixels[i*4] + pixels[i*4+1] + pixels[i*4+2]) / 3;
pixels[i*4] = average;
pixels[i*4+1] = average;
pixels[i*4+2] = average;
// On n'y touche pas car on ne veut pas appliquer de transparence
// pixels[i*4+3] = pour l'alpha
}
context.putImageData(imageData, 0, 0);
}
);
Ici, j'ai fait exprès pour que les dimensions du canvas soient exactement de la même grandeur que l'image à manipuler. Lorsqu'on récupère les informations de l'image, on se trouve à récupérer un long tableau qui décrit chaque pixels un à un. Chacun comprend quatre propriétés soient le rouge, vert, bleu et l'alpha (transparence) qui sont stockés de façon linéaire dans le tableau. Donc le premier pixel correspond aux valeurs des indices 0 à 3 où les trois premiers sont pour le RGB et le quatrième pour l'alpha. Pour le pixel suivant, on se déplace 4 indices plus loin (offset de 4) pour travailler sur les indices 4 à 7. On finit par faire une itération sur l'ensemble du tableau pour remplacer chaque pixel par un ton de gris obtenu en calculant la moyenne de chaque couleur RGB. C'est aussi simple que ça.
Vous m'excuserez si je n'ai pas été très actif sur mon blogue et Twitter ces derniers temps. Je n'ai pas abandonné. Du moins, pas encore. Le fait est que j'héberge de la famille en visite à Montréal pour quelques semaines. Si vous êtes invités chez moi, vous verrez que je traite bien mes convives : j'offre le service de taxi entre la maison et l'aéroport, l'accès au frigo est libre et gratuit, une chambre d'amis et salle de bain privée sont à leur disposition et je laisse même la clé pour aller et venir à leur guise. J'ose croire que ça fait de moi un bon hôte. Ça me fait plaisir de recevoir mais ça vient avec quelques sacrifices : moins d'intimité, reporter le visionnement des séries télé à plus tard, ne pas se promener en bobettes quand bon nous semble (j'ai même acheté un pijama pour l'occasion), laisser sa guitare de côté, voir moins souvent les amis, etc.
L'autre raison est que j'ai effectué beaucoup de temps supplémentaire au bureau. Un projet ambitieux qui a monopolisé une bonne partie de mon temps libre et qui venait avec son lot de stress, quelques cauchemars et de l'insomnie. Que voulez-vous, c'est un pari risqué d'engager un consultant en fin de projet pour faire table rase sur une stratégie qui était sur le point de se concrétiser pour finalement tout remplacer par une solution qui avait été présentée comme magique et qu'en pratique, venait avec ses failles. Remplacer des défauts par d'autres, c'est surtout une question d'accepter ceux avec lesquels on est prêt à vivre avec. Depuis le début, mon avis personnel est que c'était un canon pour tuer une mouche et qu'on y serait venu à bout à moindre coût et surtout plus rapidement. Après y avoir mis beaucoup d'efforts, le projet s'est avéré une réussite. Tous ont beaucoup appris. Le patron est heureux et a parlé de bonus. J'ai déjà hâte d'en voir la couleur, la seule compensation pour ne pas avoir eu de qualité de vie durant un moment.
Dès aujourd'hui, c'est le retour à la normale. Je compte bien en profiter et rattrapter le temps perdu.
Quoi faire quand le VPN nous déconnecte après 3 minutes ?
C'est toujours quand il y a une urgence que ce genre de chose arrive. Je devais travailler à distance en me connectant par VPN et en utilisant Remote Desktop (mstsc) pour accéder à mon poste de travail pour corriger un problème sans avoir à me déplacer physiquement au bureau. Mais voilà, la connexion fait des caprices : après exactement 3 minutes, le VPN fige, Remote Desktop plante et je dois me reconnecter à nouveau pour poursuivre mon travail. Et le processus se répète sans cesse en me laissant un délais de 180 secondes, pas une de plus ou de moins, pour accomplir une tâche quelconque.
Une fois l'urgence passée, j'ai tenté de comprendre ce qui se passait. Est-ce un conflit d'adresse IP dans le réseau local ? Mon routeur sans-fil qui est défectueux ? Des interférences d'ondes avec des téléphones sans fil (oui, j'ai entendu des théories là-dessus) ?
Pourtant, je n'ai pas le souvenir d'avoir changé quoi que ce soit dans mon réseau depuis mon déménagement ou d'avoir réinitialisé mon routeur. C'est un D-Link modèle DIR-615 qui ne date pas d'hier, donc c'était par là que je devais commencer à chercher. J'ai donc réinitialisé complètement l'appareil et appliqué un mot de passe (car évidemment, il n'y en a aucun par défaut!) en accédant à l'outil de configuration en entrant l'adresse IP de l'appareil dans la barre d'adresse du fureteur (192.168.0.x). J'en ai aussi profité pour appliquer une mise à jour du firmware (Tools / Firmware) en installant la plus récente patch qui datait... de mai 2008.
Un nouvel essai à la connexion VPN s'est soldé par un échec dès que le compteur dépassa les 3 minutes. Même les administrateurs réseau à mon travail n'avaient pas de solution à proposer. Mis à part de dire : D-Link, c'est de la cochonnerie. À ce moment-là, j'ai fini par abandonner. Pour finalement trouver une solution sur ServerFault plusieurs mois plus tard alors que quelqu'un avait rencontré exactement le même problème que moi. Et j'ai pu appliquer la même solution :
- Onglet Advanced (en haut)
- Firewall settings (à gauche)
- Au bas complètement, dans l'encadré Application Level Gateway (ALG) Configuration, décocher PPTP
- Cliquer sur le bouton Save Settings (en haut)
- Le routeur demandera une confirmation à redémarrer (Reboot Now)
- Attendre 15 secondes. Un message apparaîtra : The gateway is currently measuring your network connection. Accessing this web page might have an effect on the measurement. This page will refresh shortly.
- Lorsque l'écran d'identification réapparaîtra, il sera possible de se reconnecter au VPN. Ça devrait fonctionner.
Est-ce la bonne méthode pour corriger ce type de problème ? Pour moi, ça a bien fonctionné. J'aimerais avoir une confirmation ou une explication plus poussée car honnêtement, la réseautique n'est pas ma force. Mais une chose est certaine, le VPN ne me rejete plus comme si j'étais un indésirable.
On a souvent vu comment le faire dans Photoshop et par CSS, la réflexion d'une image est un effet très populaire allant même jusqu'à l'affichage CoverFlow d'iTunes. Peut-être même un peu surutilisé mais bon, je ne suis pas ici pour donner mon avis car je ne suis qu'un programmeur. Ce qui m'intéresse, c'est de savoir comment le faire par le canvas HTML5.
Le concept est simple : on part d'une image, on la duplique, on applique une rotation de 180 degrés à sa copie et on la positionne au pied de l'image d'origine. Pour que l'effet soit réussi, un voile partiellement transparent et dégradé vient créer un effet miroir.
Comment reproduire cet effet en appliquant les concepts du HTML5 ? En premier lieu, il faudra choisir une image. J'ai sélectionné pour vous la pochette de la trame sonore du film Back to the Future II (oui, vous pouvez aussi prendre celle de Tobby le joueur étoile, c'est selon vos goûts).
L'image mesure 200 pixels de côté et on commence par l'incorporer dans le canvas. Un point important à noter, c'est qu'on pourra la dessiner que lorsque son contenu sera chargé. C'est pourquoi on la dessinera uniquement lorsque jQuery nous indiquera le bon moment.
var canvas = $('#myCanvas');On déplace le contexte immédiatement sous l'image en calculant deux fois la hauteur de celle-ci (à cause de la copie). Et on fait pivoter le contexte verticalement à l'aide de la fonction scale (à noter que pour un pivot horizontal, ce sera scale(-1, 1)).
var context = canvas.get(0).getContext('2d');
var img = new Image();
img.src = 'images/album-os-bttf.jpg';
$(img).load(
function(){
context.drawImage(img, 0, 0);
// le reste du code sera inséré ici
}
}
context.translate(0, 400);Avant de dessiner l'image qui servira au reflet, je modifie la propriété globalAlpha qui prend une valeur entre 0 (transparent) et 1 (opaque). La seconde image sera donc semi-transparente.
context.scale(1, -1);
context.globalAlpha = 0.5;Je réinitialise l'opacité complète.
context.drawImage(img, 0, 0);
context.globalAlpha = 1;La dernière étape servira à produire le dégradé. En HTML5, il en existe un linéaire et radial (circulaire). J'utiliserai le premier qui appliquera par dessus le reflet un dégradé allant de transparent au haut jusqu'à une opacité de couleur blanche qui prendra effet au milieu de l'image jusqu'au bas (mon canvas est de fond blanc). Et on appelle fillRect() pour couvrir la totalité de l'image en reflet.
var gradient = context.createLinearGradient(0, 0, 0, 200);C'est aussi simple que ça. Le code final pour ceux qui voudraient le copier/coller :
gradient.addColorStop(1, 'transparent');
gradient.addColorStop(0.5, 'white');
context.fillStyle = gradient;
context.fillRect(0, 0, 200, 200);
var canvas = $('#myCanvas');
if(canvas && canvas.get(0).getContext){
var context = canvas.get(0).getContext('2d');
var img = new Image();
img.src = 'images/album-os-bttf.jpg';
$(img).load(
function(){
context.drawImage(img, 0, 0);
context.translate(0, 400);
context.scale(1, -1);
context.globalAlpha = 0.5;
context.drawImage(img, 0, 0);
context.globalAlpha = 1;
var gradient = context.createLinearGradient(0, 0, 0, 200);
gradient.addColorStop(1, 'transparent');
gradient.addColorStop(0.5, 'white');
context.fillStyle = gradient;
context.fillRect(0, 0, 200, 200);
}
);
}
La dernière fois que j'ai vu un film en 3D, c'était au cinéma IMAX du Centre des sciences de Montréal. L'établissement prêtait les lunettes nécessaires pouvoir profiter au maximum des effets visuels et il fallait les remettre à la fin de la représentation. Hier, je suis allé voir Tintin au cinéma Guzzo méga-plex-18-salles-avec-full-arcades-et-auto-tamponneuses du Marché Central. Pour ce film, on demandait un supplément de 3$ par personne et on avait la consolation de pouvoir garder les barniques.
La dernière tendance fière allure.
À la sortie de la salle, je me demandais bien ce que je ferais de ces lunettes. Qui sait, peut-être que je pourrais les rapporter au prochain visionnement et éviter de payer le supplément ? En regardant l'intérieur des branches, le fabricant, MasterImage3D, a pris soin d'indiquer deux choses de la plus haute importance :
- que les lunettes sont recyclables (catégorie 7 : autres plastiques)
- un avertissement : ne pas utiliser en tant que lunettes de soleil (Not For Use as Sunglasses)
Citation no. 145 sur les langages de programmation
Un ordinateur sans COBOL et FORTRAN est comme une part de gâteau au chocolat sans ketchup ni moutarde.
Créer un fichier image à partir du dataURL avec PHP
Suite au billet d'hier qui expliquait comment enregistrer le contenu d'un canvas HTML5, j'ai mentionné qu'on pouvait utiliser un langage comme PHP pour récupérer le dataURL et en faire un fichier sur le disque. Je voulais apporter une précision à ce sujet, vous allez voir, c'est loin d'être compliqué.
On peut imaginer que la valeur du dataURL serait envoyée par Ajax à un script PHP. Comme on a vu que l'URL est composé du mime-type (PNG) et du contenu de l'image encodé en base64, on doit d'abord extraire cette portion et ensuite la décoder.
// donnée raccourcie pour faciliter la lectureUne fois en possession de la donnée binaire, on peut utiliser une combinaire de fopen/fwrite/fclose pour écrire le contenu dans un fichier physique.
$dataURL = 'data:image/png;base64,iVB[...]rkJggg==';
$parts = explode(',', $dataURL);
$data = $parts[1];
$data = base64_decode($data);
$fp = fopen('exemple-1.png', 'w');Ou encore plus simple, en une seule ligne.
fwrite($fp, $data);
fclose($fp);
file_put_contents('exemple-2.png', $data);Ça devient un peu plus compliqué si on veut apporter des modifications à l'image avant de l'enregistrer. Par exemple, l'extension GD (qui est souvent installée par défaut sur les serveurs) permet de récupérer la source du contenu sous forme de texte.
L'image du canvas est transparente. Il faudra donc appliquer la valeur true à imagesavealpha, sans quoi la couleur de fond deviendra noire par défaut. Ensuite, on appliquera les altérations voulues (par exemple combiner deux images, apposer le logo de la compagnie, etc) et on enregistrera le résultat à l'aide de la fonction imagepng().
À noter que si vous avez appliqué une couleur de fond en CSS à la balise canvas (comme j'ai fait dans l'exemple de spirale), cette couleur ne fait pas parti de l'image lors de l'enregistrement. Vous devrez soit l'appliquer directement au canvas, soit ajouter la couleur de fond de votre choix pour remplacer la transparence par GD.
$img = imagecreatefromstring($data);Dans les deux premiers exemples, la poids des images enregistrées était de 19 kb. Avec les paramètres par défaut de GD, l'enregistrement de la même image produisait un fichier de 23 kb. Si vous avez un souçi d'optimisation de l'espace de stockage, ajustez le paramètre de compression de la fonction imagepng() car le poids varie énormément.
if($img !== false){
// pour la transparence
imagesavealpha($img, true);
// modifications ici
imagepng($img, 'exemple-3.png');
imagedestroy($img);
}
else{
echo 'Erreur';
}
// par défaut
imagepng($img, 'exemple-3.png'); // 19 kb
// compression maximale
imagepng($img, 'exemple-3.png', 9, PNG_NO_FILTER); // 16.5 kb
// aucune compression
imagepng($img, 'exemple-3.png', 0, PNG_NO_FILTER); // 978 kb !
Dans les dernières semaines, je me suis plongé dans différentes expérimentations pour découvrir le potentiel du canvas HTML5 :
- Canvas 2D en 10 étapes faciles / barres de calibrage d'un téléviseur (cours 101)
- Logo de Radio-Canada (arcs de cercles)
- Dessiner une spirale (radians)
- Dessiner un coeur (arcs)
- Emblème de Montréal (translations, arcs, rotation, clonage du canvas, courbes quadratiques)
Si je reprends n'importe quel de mes exemples, il suffit d'ajouter la ligne suivante à la toute fin :
var data = canvas.toDataURL();Cette fonction du canvas enregistrera dans la variable data le contenu de l'image, par défaut sous forme de PNG. Il restera ensuite à décider ce qu'on en fera.
En regardant de plus près son contenu, on voit que la valeur peut être assez longue.
data:image/png;base64,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Ce qui est important à retenir :
- data:image/png : indique le mime type du contenu
- suivi du contenu encodé en base64 pour éviter les caractères spéciaux dans l'URL
Maintenant que l'image est contenue dans l'URL, que peut-on faire ?
1. Barre d'adresse
Il est possible de copier la valeur du dataURL et la coller directement dans la barre d'adresse. Cliquer Enter et si votre fureteur est compatible (exemple : Firefox, Chrome), vous devriez voir l'image telle qu'elle a été dessinée originalement dans le canvas. C'est important de comprendre que ce ne sont pas les instructions JavaScript qui sont exécutées de nouveau mais bel et bien l'image PNG, telle qu'elle aurait été prise par un imprimé d'écran (de la grandeur définie par les propriétés du canvas).
2. Dans une balise <img>
C'est aussi possible d'utiliser la valeur comme source d'une balise <img> au lieu de pointer vers un nom de fichier. Ça peut se faire lors de manipulations du DOM par le JavaScript (imaginez avec jQuery : $('#myImageTag').attr('src', data)). Sinon, pourquoi ne pas conserver l'URL dans une base de données en vue d'une récupération future ou juste créer un fichier physique sur le serveur à partir de la source base64 ?
3. Enregistrer l'image avec Firefox
Firefox possède une fonctionnalité native permettant d'enregistrer l'image directement à partir du canvas à l'aide du menu contextuel (click droit sur le canvas, Save Image As).
Pour terminer, j'avais déjà noté avec l'éditeur RTE de Blogger (à partir de Firefox) qu'il est possible de glisser une image de mon PC vers la boîte d'édition (en mode composition / WYSIWYG) pour l'inclure dans un billet. L'image n'est pas transférée sur le serveur en tant que fichier, c'est plutôt le base64 du contenu du PNG qui est copié en texte dans la source d'une balise <img> (tel que décrit au point 2). Autrement dit, si un site web ne me permettait pas de transférer ou d'héberger une image sur ses serveurs mais que certaines balises HTML étaient permises, je pourrais soit faire un hotlink vers une image d'un serveur tiers, soit tenter d'inclure une image par sa source texte/base64 (à condition que le champ de sa base de données soit suffisamment grand pour la stocker, par exemple un champ text versus une limite de 500 caractères sur un varchar). C'est à essayer.