Optimisation des images, comprendre la « dirty transparency »

Les consignes webperf un peu partout vous recommanderont de réduire la taille de vos images. Pour cela, il suffit d’utiliser des logiciels appropriés afin de supprimer toutes les méta-données qui pourrait alourdir le composant graphique.

Lorsque l’on travaille sur une image de type PNG-24, avec de la semi-transparence, on se retrouve confronté à un gros surplus d’informations causés par la transparence, appelé « dirty transparency » par les anglophones.

Avant d’expliquer ce dont il s’agit, il faut revoir la structure du format PNG :
Le PNG-24 est composé de trois informations de couleur : Rouge-Vert-Bleu, avec chacune de ces informations codé sur… 24/3 = 8 bits, soit 256 (28) teintes différentes.
En fait, le fonctionnement est identique aux couleurs hexadécimales que l’on utilise en CSS par exemple, mais répété pour chaque point.

Premier point : R-255, V-255, B-255
Deuxième point : R-253, V-254, B-255
Troisième point : R-251, V-253, B-255
etc.

Une fois l’image traitée dans sa totalité, le PNG est compressé afin d’obtenir une image de plus petite taille.

Jusque là, la seule optimisation possible de la taille de l’image résulte de l’utilisation de différentes options pour que la compression des données brutes soit optimale.

Lorsque l’on induit la transparence, On ajoute en une information de plus : le niveau de transparence, sur 8 bits soit 256 (28) niveaux de transparence.

Premier point : R-255, V-255, B-255, A-30
Deuxième point : R-253, V-254, B-255, A-0
Troisième point : R-251, V-253, B-255, A-0
etc.

Quelle est la différence ? Seulement le canal « Alpha » qui donne l’information du niveau de transparence pour le pixel.
L’image continue d’exister avec toutes les informations de couleur, même si le pixel est déclaré « totalement transparent », et, si toutes ces informations sont différentes comme dans l’exemple montré ci-dessous, la compression ne sera jamais optimale.

Heureusement, ce problème du format PNG est facile à contourner : Il suffit de remplacer tout les pixels transparents (Soit alpha = 0) par une même couleur, totalement transparente :

Premier point : R-255, V-255, B-255, A-30
Deuxième point : R-0, V-0, B-0, A-0
Troisième point : R-0, V-0, B-0, A-0
etc.

Ainsi, la compression de l’image sera bien meilleure, car le pattern le pattern « pixel transparent » est le même dans toute l’image. J’ai personnellement réussi à diviser la taille de l’image jusqu’à 3.

Pour arriver à ceci, j’utilise le code php suivant, que je vous recommande d’utiliser une seule fois, à la mise en production par exemple, puisqu’il est assez coûteux en ressources :

<?php

$i = '/path/to/png/file';
$o = '/path/to/output/file';

$in = imagecreatefrompng($i);

imagealphablending($in, false);
imagesavealpha($in, true);

$x = imagesx($in);
$y = imagesy($in);

$mx = 0;
while ($mx < $x) {
    $my = 0;
    while ($my < $y) {
        $c = imagecolorat($in, $mx, $my);
        if (($c & 0x7F000000) == 0x7F000000) {
            imagesetpixel($in, $mx, $my, 0x7F000000);
        }
        $my++;
    }
    $mx++;
}

imagepng($in, $o, 9);

Une fois le script passé, vous pouvez/devez aussi passer l’image dans votre compresseur classique afin de gagner encore quelques octets sur la taille de vos images.

Publicités

Quick Tip – Optimisation simple de boucles JavaScript

N’étant pas resté au bout de la soirée webperf du 21 avril, je ne me risquerais pas à faire un compte rendu qui risquerait d’être incomplet.

Néanmoins, lors de cette soirée nous avons découvert un morceau de code assez surprenant.

Pour se mettre dans le contexte, nous étions en train d’analyser le site de 20 minutes, et
Vincent Voyer
à décidé de lancer un dynatrace.

En regardant les résutlats, nous avons remarqué qu’un script avait un temps d’execution de… 1.1s, ce qui est bien entendu énorme ! Ce script vennait du tag xiti. Celui-ci sera peut-être corrigé, mais il pourra servir de cas d’exemple sur une mauvaise pratique.

En fouillant dans le code on découvre (en réindenté) le code suivant :

var elts = target.getElementsByTagName('*');
for (var i = 0; i < elts.length; i++) {
...
}

Pourquoi est-ce un problème ? Avant de commencer à expliquer le problème, il est important de connaitre un point important. Pour celui-là, j’ai utilisé statsy.

JS in HTML attributes: 1351 bytes
CSS in HTML attributes: 2790 bytes
JS in SCRIPT tag: 4111 bytes
CSS in STYLE tag: 69039 bytes
All innerHTML: 215207 bytes
# SCRIPT tag: 54
# STYLE tag: 1
# DOM elements: 2143
Cookie size: 340 bytes

Dans la liste des résultats ci-dessus, on remarque que la page comporte 2143 élements. Le problème est simple :
Le getElementsByTagName récupère tout les éléments DOM. Ce qui demande beaucoup de traitement sur une page kilométrique comme celle d’un site de news.

Second problème, la boucle effectuée sur ces éléments recalcule cette selection à chaque itération de la boucle. Les élements sont donc recalculés 2143 fois. C’est simplement énorme.

Comment corriger le problème ? Pour le premier, il faut savoir si cette selection est vraiment importante. Qu’à-t-on besoin de faire sur tout l’arbre DOM ?
Pour le second, il suffit de mettre en cache la taille du tableau, par exemple comme ceci :

var elts = target.getElementsByTagName('*');
for (var i = 0, len = elts.length; i < len; i++) {
...
}

Et dans ces conditions, le recalcul ne sera pas refait pour chaque itération.

Conclusion :
De plus en plus, les sites sont remplis de JavaScript. Ces améliorations peuvent sembler superflues, mais c’est ce qui peut faire la différence entre deux site.
Si vous n’en êtes pas convaincus, pensez aux utilisateurs de netbook, de tablettes ou de mobile : Vous allez leur faire gagner un temps fou !

Statsy v2 – Quelques ajouts

Stoyan Stefanov à présenté Stasty, un bookmarklet qui permet d’obtenir très rapidement des informations notamment sur les style et script présents en inline sur une page.
Cependant, il manque sur ce script quelques informations importantes, facilement récupérables.
J’ai donc créé un nouveau bookmarklet avec des infomations un peu plus complètes.

Stoyan Stefanov à présenté Stasty, un bookmarklet qui permet d’obtenir très rapidement des informations notamment sur les style et script présents en inline sur une page.

Cependant, il manque sur ce script quelques informations importantes, facilement récupérables.

J’ai donc créé un nouveau bookmarklet avec des infomations un peu plus complètes :

Statsy 2
(function(){
    var jsattribs = [
        'onbeforeunload',
        'onclick',
        'ondblclick',
        'onerror'
        'onload',
        'onmousedown',
        'onmousemove',
        'onmouseout',
        'onmouseover',
        'onmouseup',
        'onunload',
    ],
    cssattribs = [
        'style'
    ];

    var all_elems = document.getElementsByTagName('*');

    function getAttribsSize(attribs) {
        var value = '',
            attr = '',
            cnt = 0;

        for (var i = 0; i < all_elems.length; i++) {
            for (var j = 0; j < attribs.length; j++) {
                attr = attribs[j];
                value = all_elems[i].getAttribute(attr);
                if (value && typeof value === 'string') {
                    cnt += attr.length;
                    cnt += 3; // ="..."
                    cnt += value.length;
                }
            }
        }
        return cnt;
    }

    function getInlineSize(tag) {
        var s = 0,
            all = document.getElementsByTagName(tag);
        for (var i = 0; i < all.length; i++) {
            s += all[i].innerHTML.length;
        }
        return s;
    }

    var jsatt = getAttribsSize(jsattribs);
    var cssatt = getAttribsSize(cssattribs);

    var msg = [];
    msg.push('JS in HTML attributes: '+jsatt+' bytes');
    msg.push('CSS in HTML attributes: '+cssatt+' bytes');
    msg.push('JS in SCRIPT tag: '+getInlineSize('script')+' bytes');
    msg.push('CSS in STYLE tag: '+getInlineSize('style')+' bytes');
    msg.push('All innerHTML: '+document.documentElement.innerHTML.length+' bytes');
    msg.push('# SCRIPT tag: '+document.getElementsByTagName('script').length);
    msg.push('# STYLE tag: '+document.getElementsByTagName('style').length);
    msg.push('# DOM elements: '+all_elems.length);
    msg.push('Cookie size: '+document.cookie.length+' bytes');

    alert(msg.join("n"));
})();

Vous pouvez directement récupérer le bookmarklet ici :

Statsy 2

N’hésitez pas à faire vos suggestions !

UPDATE 30/11/2010

Suite à la demande de jpvincent, petit explicatif des différentes informations remontées :

JS in HTML attributes : Cette information remonte tout le code JavaScript présent directement dans les balises HTML en tant qu’attribut. Par exemple, une page qui contiendrait <h1 onmouseover= »alert(‘H1’); »></h1> ajouterait 26 bits à JS in HTML attributes

CSS in HTML attributes : Même procédé que pour les JavaScript détaillés un peu plus haut, en utilisant cette fois le contenu des attributs style (ex. style= »color:#f00; »)

JS in SCRIPT tag : Cette information remonte le nombre de bits du code JavaScript présent au sein de balises

JCSS in STYLE tag : Même principe que la propriété précédente, cette information remonte le nombre de bits du code CSS présent au sein de balises <style></style>

# SCRIPT tag : Compte le nombre d’occurrence d’utilisation des balises <script> au sein de la page

# STYLE tag : Compte le nombre d’occurrence d’utilisation des balises <style> au sein de la page

# DOM elements : Compte le nombre d’éléments DOM au sein de la page

Cookie size : Retourne la taille en bit des cookies présent sur le fichier HTML, et donc probablement sur tout les fichiers statiques s’ils ne sont pas sur un domaine externe