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 bouche.

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

À la demande confuse générale, voici un exemple tout simple qui ravira les petits comme les grands. Nous allons charger une image, la redimensionner en 128×128 et renvoyer la version redimensionnée au format PNG.

Pour cet exemple, j'utilise la méthode RESIZE_SURFACE. Elle ne respecte pas vraiment les dimensions de destination, mais plutôt sa surface. J'ai choisi cette méthode pour justifier l'utilisation de variables pour passer les dimensions cibles, puisque les dimensions finales seront écrites dans ces variables à la fin du redimensionnement.

J'espère que ce sera assez clair, et utile !

<?php

#
# voici l'adresse de l'image que nous voulons redimensionner.
# il est recommandé d'utiliser un chemin absolu. dans notre exemple
# l'image se trouve au même niveau que notre script, alors on fait les
# fégniants :-)
#

$file = 'image-resize-source.jpg';

#
# on crée une ressource _image_ depuis le fichier
#

$image = imagecreatefromjpeg($file);

#
# on redimensionne l'image en 128x128 en utilisant la méthode
# RESIZE_SURFACE.
#
# note: les dimensions *doivent* être passées sous forme de
# variables. Elles sont utilisées comme _référence_. La méthode
# RESIZE_SURFACE y écrira les dimensions finales de l'image.
# Ici ce sera 181x91 (16384 => 16471)
#

$w = 128;
$h = 128;

$result = WdImage::resize($image$w$h, WdImage::RESIZE_SURFACE);

#
# on renvoie le résultat sous forme d'image png en utilisant
# un callback sur le tampon de sortie
#

function output_handler($img)
{
    header('Content-type: image/png');
    header('Content-Length: ' . strlen($img));

    return $img;
}

ob_start('output_handler');

imagepng($result, null);

ob_end_flush();

exit;

?>

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;
    }
}

?>

6 commentaires

1
SkiTo a écrit : Dimanche 06 Juillet 2008 à 11:34

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

2
Jp a écrit : Mardi 15 Juillet 2008 à 14:40

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

3
Olivier a écrit : Dimanche 27 Juillet 2008 à 11:18

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 ;-)

4
Btacquet a écrit : Mardi 24 Mars 2009 à 10:35

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.

5
Olivier a écrit : Dimanche 29 Mars 2009 à 17:37

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 !

6
Mademoisl a écrit : Mardi 31 Mars 2009 à 21:51

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!

Laisser un commentaire

 
 Souhaitez-vous être informé par E-Mail de la réponse à votre message ?