Une classe et sept méthodes
pour créer des miniatures

Les miniatures c'est chouette, mais les faire avec Photoshop c'est déjà moins chouette. Surtout quand on gère un album web dans lequel de nombreuses personnes peuvent laisser des images. Il est possible de les redimensionner par l'intermédiaire de la fonction imagecopyresampled() mais on ne peut pas dire que l'on soit étouffé sous une pléthore d'options de redimensionnement et de recadrage.

Je vous propose donc la méthode resize() de ma classe WdImage et quelques exemples de rendu pour vous mettre l'eau à la louche.

Mise à jour 2010-02-18 : Le code suivant vous donne du fil à retordre ? Consultez l'article Une démonstration toute prête des méthodes de redimensionnement de la classe WdImage et retrouvez les champs fleuris de votre enfance !

Sept méthodes de redimensionnement

Les exemples suivant utilisent trois images aux formats bien différents : un carré (300 × 300px), un rectangle horizontal (400 × 200px) et un rectangle vertical (200 × 400px). Elles sont ensuite redimensionnées en 100 × 200px.

Voilà ce que cela donne en fonction des options de redimensionnement choisies :

WdImage::RESIZE_SCALE_MAX

Redimensionne l'image pour quelle tienne dans l'espace cible. Cela donne des miniatures ayant toutes la même largeur et la même hauteur, avec un fond visible.

resize-scale-max

WdImage::RESIZE_SCALE_MIN

Redimensionne l'image pour que tout l'espace cible soit rempli. L'image est cadrée horizontalement et verticalement. Cela donne des miniatures ayant toutes la même largeur et la même hauteur, avec un maximum de l'image source visible.

resize-scale-min

WdImage::RESIZE_FIXED_HEIGHT

L'image est redimensionnée par rapport à la hauteur cible. La largeur est calculée proportionnellement. Cela donne des miniatures qui ont toutes la même hauteur, mais pas forcément la même largeur.

resize-fixed-height

WdImage::RESIZE_FIXED_HEIGHT_CROPPED

L'image est redimensionnée par rapport à la hauteur cible. Si la largeur de l'image est plus importante que celle de la cible, l'image est cadrée. Cela donne des miniatures qui ont toutes la même hauteur, mais pas forcément la même largeur.

resize-fixed-height-cropped

WdImage::RESIZE_FIXED_WIDTH

L'image est redimensionnée par rapport à la largeur cible. La hauteur de l'image est calculée proportionnellement. Cela donne des miniatures qui ont toutes la même largeur, mais pas forcément la même hauteur.

resize-fixed-width

WdImage::RESIZE_FIXED_WIDTH_CROPPED

L'image est redimensionnée par rapport à la largeur cible. Si la hauteur de l'image est plus importante que celle de la cible, l'image est cadrée. Cela donne des miniatures qui ont toutes la même largeur, mais pas forcément la même hauteur.

resize-fixed-width-cropped

WdImage::RESIZE_SURFACE

L'image est redimensionnée pour que sa surface égale celle de la cible. Cela donne des miniatures de largeurs et de hauteurs différentes mais qui occupent toutes la même surface en terme de pixels.

resize-surface

Un exemple d'utilisation

<?php

$thumbnail = WdImage::resize
(
    $source,
            
    $target_width,
    $target_height,
            
    WdImage::RESIZE_SCALE_MAX,

    'fill_callback'
);

Comme vous pouvez le voir dans l'exemple ci-dessus, il est possible de définir la fonction qui sera appelée pour remplir la zone cible avant de redimensionner l'image source. J'en profite pour utiliser la méthode drawGrid() que je vous avez présenté précédemment.

<?php

function fill_callback($image$w$h)
{
    WdImage::drawGrid
    (
        $image00$w - 1$h - 1
    );
}

Un exemple d'utilisation dans la vrai vie

Mise à jour 2010-02-18 : Consultez l'article Une démonstration toute prête des méthodes de redimensionnement de la classe WdImage afin de télécharger une archive contenant le nécessaire à une démonstration sans douleur.

Une méthode bien méritée

Voici donc la méthode resize() extraite de la classe WdImage :

<?php

class WdImage
{
    const RESIZE_SCALE_MAX = 'scale-max';
    const RESIZE_SCALE_MIN = 'scale-min';
    const RESIZE_FIXED_HEIGHT = 'fixed-height';
    const RESIZE_FIXED_HEIGHT_CROPPED = 'fixed-height-cropped';
    const RESIZE_FIXED_WIDTH = 'fixed-width';
    const RESIZE_FIXED_WIDTH_CROPPED = 'fixed-width-cropped';
    const RESIZE_SURFACE = 'surface';
    
    static public function resize($source&$t_w&$t_h$method$fill_callback=NULL)
    {
        #
        # source dimensions
        #
        
        $s_x = 0;
        $s_y = 0;
        $s_w = imagesx($source);
        $s_h = imagesy($source);
        
        #
        # destination dimensions
        #

        $d_x = 0;
        $d_y = 0;
        $d_w = $t_w;
        $d_h = $t_h;
        
        #
        # select scale method
        #
        
        switch ($method)
        {
            case self::RESIZE_SCALE_MAX:
            default:
            {
                #
                # scale-max
                #
                # Resize the image so that it all fits in the target space.
                # This will result in thumbnails equals in width and height, with a possible
                # background visible.
                #
                
                $s_r = $s_w / $s_h;
                $d_r = $d_w / $d_h;
                
                $r = $s_r > $d_r ? $s_w / $d_w : $s_h / $d_h;
    
                $d_w = round($s_w / $r);
                $d_h = round($s_h / $r);
            }
            break;
    
            case self::RESIZE_SCALE_MIN:
            {
                #
                # scale - min
                #
                # Resize the image so that the whole target space is filled. The image is cropped
                # to the target width and height.
                # This will result in thumbnails equals in width and height, with the maximum of
                # information visible.
                #
                
                $s_r = $s_w / $s_h;
                $d_r = $d_w / $d_h;
                
                if ($s_r > $d_r)
                {
                    $r = $s_h / $d_h;
                    $s_x += round(($s_w - $t_w * $r) / 2);
                }
                else
                {
                    $r = $s_w / $d_w;                   
                    $s_y += round(($s_h - $t_h * $r) / 2);
                }
                
                $d_w = round($s_w / $r);
                $d_h = round($s_h / $r);
            }
            break;

            case self::RESIZE_FIXED_HEIGHT:
            {
                #
                # fixed-height
                #
                # The image is resized to match the target height.
                # The image width is resized accordingly.
                # This will result in thumbnails equals in height, but not in width.
                #
                
                $r = $s_h / $d_h;

                $d_w = round($s_w / $r);
                
                if ($s_w > $s_h)
                {
                    $d_h = round($s_h / $r);                    
                }

                $t_w = $d_w;
            }
            break;
            
            case self::RESIZE_FIXED_HEIGHT_CROPPED:
            {
                #
                # fixed-height-cropped
                #
                # The image is resized to match the target height.
                # If the image width is larger than the target width, the image is cropped.
                # This will result in thumbnails equals in height, but not in width.
                #
                
                $r = $s_h / $d_h;

                $d_w = round($s_w / $r);
                
                if ($s_w > $s_h)
                {
                    $d_h = round($s_h / $r);                    
                }
                else
                {
                    $t_w = $d_w;
                }               

                #
                # crop image
                #

                if ($s_w > $t_w * $r)
                {
                    $s_x += round(($s_w - $t_w * $r) / 2);
                }
            }
            break;

            case self::RESIZE_FIXED_WIDTH:
            {
                #
                # fixed-width
                #
                # The image is resized to match the target width.
                # The image height resized accordingly.
                # This will result in thumbnails equals in width, but not in height.
                #
                
                $r = $s_w / $d_w;

                $d_h = round($s_h / $r);
    
                if ($s_w < $s_h)
                {
                    $d_w = round($s_w / $r);                    
                }
                
                $t_h = $d_h;
            }
            break;

            case self::RESIZE_FIXED_WIDTH_CROPPED:
            {
                #
                # fixed-width-cropped
                #
                # The image is resized to match the target width.
                # If the image height is taller than the target height, the image is cropped.
                # This will result in thumbnails equals in width, but not in height.
                #
                
                $r = $s_w / $d_w;

                $d_h = round($s_h / $r);
    
                if ($s_w > $s_h)
                {
                    $t_h = $d_h;
                }
                else
                {
                    $d_w = round($s_w / $r);                    
                }
                
                #
                # crop image
                #

                if ($s_h > $t_h * $r)
                {
                    $s_y += round(($s_h - $t_h * $r) / 2);
                }
            }
            break;
        
            case self::RESIZE_SURFACE:
            {
                #
                # surface
                #
                # The image is resized so to its surface matches the target surface.
                # this will result in thumbnails with different width and height, but with
                # the same amount of pixels.
                #
                
                $r = sqrt(($s_w * $s_h) / ($t_w * $t_h));
                
                $d_w = round($s_w / $r);
                $d_h = round($s_h / $r);
                
                $t_w = $d_w;
                $t_h = $d_h;
    
            }
            break;
        }
        
        #
        # center destination image result
        #
        
        if ($t_h > $d_h)
        {
            $d_y = round(($t_h - $d_h) / 2);
        }
        
        if ($t_w > $d_w)
        {
            $d_x = round(($t_w - $d_w) / 2);
        }
        
        #
        # create destination image
        #       
        
        $destination = imagecreatetruecolor($t_w$t_h);

        #
        # call user's function to fill the area
        #
        
        if ($fill_callback)
        {
            call_user_func($fill_callback$destination$t_w$t_h);
        }

        #
        # now we resize and paste our image
        #
        
        imagecopyresampled
        (
            $destination,
            $source,
            
            $d_x$d_y$s_x$s_y,
            $d_w$d_h$s_w$s_h
        );
        
        return $destination;
    }
}

Laisser un commentaire

13 commentaires

skiTo
skiTo

Salut à toi,

ton script est très bien foutu… mais j'aimerai bien savoir comment faire pour l'utiliser et surtout pour appeler la classe et les variables à envoyer….

Merci d'avance

jp
jp

moi c'est pareil, car ca n'est pas très clair à mes yeux

Olivier
Olivier

J'ai ajouté Un exemple d'utilisation dans la vrai vie. J'espère qu'il sera assez clair et surtout utile. N'hésitez pas à venir râler si vous n'y comprenez toujours rien ;-)

Btacquet
Btacquet

Bonjour,

Je suis entrain de développer un plugin pour la gestion des miniatures (mise en cache, recadrages, crop) pour le site de wikine. Je me suis permis de prendre votre script comme base pour les fonctions de retouche.

Cela vous embête-t-il ? Seriez vous intéressé par la publication libre du plugin et à une plus ample participation (en diversifiant encore les possibilité offerte ?).

Cordialement.

Olivier
Olivier

Salut Btacquet,

Aucun problème pour l'utilisation du script, il est là pour ça de toute façon :-)

Si ça t'intéresse j'ai aussi développé un module pour mon CMS qui permet de générer des miniatures à la volée (avec un cache). On utilise une simple URL dans laquelle on définit le chemin vers l'image, les dimensions souhaitées, le mode de redimensionnement et la couleur de remplissage.

Je pense que le code peut facilement être adapté, le plus important restant le principe.

Pour ce qui est de la publication libre de ton plugin, je suis pour !
Vive l'échange !

Mademoisl
Mademoisl

bonjour et tout d'abord, un grand ouah pour ton script!

je serais aussi intéressée de voir le module que tu as développer pour l'installer sur un cms!

Nico
Nico

Salut,

Même avec l'exemple je n'arrive pas à le faire fonctionner …

J'ai téléchargé le dossier avec toutes les classes … j'ai créé l'index… mais rien ne s'affiche …

Merci d'avance !

Olivier
Olivier

Pour ceux qui galèrent avec le code que je vous présente ici, merci de consulter l'article Une démonstration toute prête des méthodes de redimensionnement de la classe WdImage. J'espère que l'archive de démonstration que je vous y propose vous redonnera le sourire et apportera la paix dans le monde et dans votre cœur.

atomestudio
atomestudio

Bonjour, Merci pour cette classe. par contre y a t-il un soucis avec les fichiers .png ?

Olivier
Olivier

@Atomestudio: J'ai mis à jour la classe WdImage que contient l'archive : si aucune fonction de rappel n'est définie pour remplir le fond de l'image redimensionnée alors il sera transparent.

À noter que la démonstration ne revoie que des images au format JPEG sur un fond en damier.

Pour créer un PNG transparent tu peux faire comme ça :

<?php

$w = 256;
$h = 256;

$image = WdImage::load($path$info);
$image = WdImage::resize($image$w$h'surface');

header('Content-type: image/png');
header('Pragma: no-cache');

imagealphablending($imagefalse);
imagesavealpha($imagetrue);
imagepng($image);

J'espère avoir un peu de temps ce weekend pour présenter le module que j'ai écris pour créer à la volée des miniatures. J'espère que le code ci-dessus te permettra de patienter.

Atomestudio
Atomestudio

Ok merci je vais regarder ta proposition.

Eric
Eric

Salut, votre framework a l'air très pointu….c'est un bolide!!!! Mais j'arrive pas du tout à le demarrer, s'il n' y a pas de documentation…le demarrage du « coeur » au moins serait le minimum à mettre en ligne pour nous autres qui debutons en programmation….. Merci et bien à vous

Kadice
Kadice

Même avec le temps, ce script est toujours aussi efficace. Dès que j'ai besoin d'un script qui fait du resizing, je cours vers celui ci. Merci man !