Ce projet à pour objectif de manipuler les opérations de gestion mémoire de type malloc, les opérations de gestion de fichier ainsi que la bibliothèque de gestion des images au format jpeg (libjpeg).

Vous devez écrire un programme qui transforme une image jpeg en une image ascii. Dans l’exemple suivant, l’image originale est une image couleur. La seconde image est la transformation de l’image originale en image ASCII dans laquelle chaque point de l’image est un caractère choisi en fonction de l’intensité et de la couleur de l’image originale.

House.jpg HouseASCI.png

Principe de la transformation

L’image originale est découpée en pavés contigus selon la géométrie de l’image finale (par exemple 80x25 caractères). Pour chaque pavé, on calcule l’intensité lumineuse moyenne et la couleur moyenne. Ces deux paramètres déterminent le caractère qui sera choisi pour le représenter.

Matricage.jpg

Le tableau suivant donne un exemple d’encodage des pixels par des caractères. Chaque ligne peut correspondre à une couleur différente, allant du plus clair au plus foncé.

.

-

/

r

L

o

*

'

_

|

c

C

a

&

`

+

(

v

J

h

%

,

<

)

u

U

k

$

^

i

1

n

Y

b

#

:

?

]

x

X

d

@

Calcul de la luminance et de la couleur : Changement d’espace colorimétrique

En informatique, les images couleurs sont la plupart du temps représentées au format RVB (RGB), où chaque pixel est composé de trois valeurs de rouge, de vert et de bleu.

Cependant, cette représentation ne sépare pas clairement la luminance, c'est-à-dire l’intensité lumineuse (claire ou sombre), de la couleur. Il existe une autre représentation des pixels appelée HSL :

  • Hue : un code pour la couleur, entre 0 et 1 ;

  • Saturation : la vivacité de la couleur, entre 0 et 1 (par exemple de rouge vif à rouge terne puis gris)

  • Luminance : l’intensité lumineuse, entre 0 et 1.


Note La couleur peut être représentée sur un cercle des couleurs. Une couleur, et donc sa valeur Hue, est alors un angle sur ce cercle (entre 0 et 360°). Pour des raisons de simplicité, on ramène cette valeur entre 0 et 1. Hue.png

Un pseudo code pour transformer du RGB en HSL est le suivant :

/**
 * Converts an RGB color value to HSL. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
 * Assumes r, g, and b are contained in the set [0, 255] and
 * returns h, s, and l in the set [0, 1].
 *
 * @param   Number  r       The red color value
 * @param   Number  g       The green color value
 * @param   Number  b       The blue color value
 * @return  Array           The HSL representation
 */
function rgbToHsl(r, g, b){
    r /= 255, g /= 255, b /= 255;
    var max = Math.max(r, g, b), min = Math.min(r, g, b);
    var h, s, l = (max + min) / 2;

    if(max == min){
        h = s = 0; // achromatic
    }else{
        var d = max - min;
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
        switch(max){
            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
            case g: h = (b - r) / d + 2; break;
            case b: h = (r - g) / d + 4; break;
        }
        h /= 6;
    }

    return [h, s, l];
}

La bibliothèque libjpeg

Le format jpeg est l’un des standards de stockage des images. C’est un format qui compresse les images selon des algorithmes sophistiqués de traitement du signal.

Une image est à la base une matrice à deux dimensions (largeur x hauteur) de pixels. L’image peut être en noir et blanc, dans ce cas le pixel sera généralement un octet représentant un certain niveau de gris (0 noir, 255 blanc), ou en couleur, dans ce cas le pixel sera composé de trois octets, un pour chaque couleur primaire rouge(R), vert(G) et bleu(B).

La librairie libjpeg propose des fonctions de lecture et d'écriture de fichiers d’image jpeg.

Les prototypes des fonctions sont décrits dans l’include jpeglib.h (rajouter aussi jerror.h pour avoir des messages d’erreurs explicites).

Les objets jpeg sont manipulés par l’intermédiaire de structures spécifiques pour la décompression et la compression.

Lecture de fichiers jpeg

Pour la décompression d’images jpeg, il faut utiliser la structure jpeg_decompress_struct. Cette structure contient trois champs qui vont nous intéresser pour la suite :

  • output_height : le nombre de lignes de l’image ;

  • output_width : le nombre de pixels par lignes ;

  • output_components : le nombre d’octets par pixels (1 pour les images N&B, 3 pour les images couleurs).

Le schéma général pour lire un fichier jpeg est le suivant.

...
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;

...
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo); // Initialisation de la structure

jpeg_stdio_src(&cinfo,file);  // file est de type FILE * (descripteur de fichier
                              // sur le fichier jpega decompresser)
jpeg_read_header(&cinfo,TRUE);// lecture des infos sur l'image jpeg
jpeg_start_decompress(&cinfo);// lancement du processus de decompression
...

while (toute les lignes pas encore lues)
 {
     ...
     jpeg_read_scanlines(&cinfo,buffer,n) // lecture des n lignes suivantes de l'image
                                          // dans le buffer (de type unsigned char *)
     ...
}

jpeg_finish_decompress(&cinfo);

jpeg_destroy_decompress(&cinfo);

Les informations sur la taille de l’image (output_height, output_width et output_components) sont disponibles après l’appel de la fonction jpeg_start_decompress.

La fonction jpeg_read_scanlines lit n lignes de l’image dans le buffer buffer. Si l’image est en couleur, chaque pixel est représenté par trois octets consécutifs, ou par un octet si elle est en N&B.

Ecriture de fichiers jpeg

De manière similaire, pour la décompression d’images jpeg, il faut utiliser la structure jpeg_compress_struct. Cette structure contient trois champs qui vont nous intéresser pour la suite :

  • image_height : le nombre de lignes de l’image ;

  • image_width : le nombre de pixels par lignes ;

  • input_components : le nombre d’octets par pixels (1 pour les images N&B, 3 pour les images couleurs) ;

  • in_color_space : l’espace des couleurs (RGB ou GRAYSCALE). Le schéma général pour écrire un fichier jpeg est le suivant.

#include <libjpeg>
#include <jerror.h>
...
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;

...
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo); // Initialisation de la structure

jpeg_stdio_dest(&cinfo,file);  // file est de type FILE * (descripteur de fichier
                              // sur le fichier jpeg compressé final)
cinfo.image_width=w;          // nombre de ligne de l'image
cinfo.image_height=h;         // nombre de pixel par ligne
cinfo.input_component=c;      // 3 pour une image couleur, 1 pour une N&B
cinfo.in_color_space=s;       // la constante JCS_RGB pour une image couleur ou
                              // JCS_GRAYSCALE pour une image N&B
jpeg_set_defaults(&cinfo);    // initialisation des paramètres de compression
jpeg_start_compress(&cinfo,TRUE); // lancement du processus de decompression
...

while (toute les lignes pas encore écrites)
 {
     ...
     jpeg_write_scanlines(&cinfo,buffer,n) // écriture des n lignes suivantes de l'image
                                          // stockées dans le buffer (de type unsigned char *)
     ...
}

jpeg_finish_compress(&cinfo);

jpeg_destroy_compress(&cinfo);

Les informations sur la taille de l’image (image_height, image_width et input_components) ainsi que sur la couleur (in_color_space) sont à initialiser avant l’appel de la fonction jpeg_set_default.

La fonction jpeg_write_scanlines écrit dans le fichier les n lignes de l’image qui sont dans le buffer buffer. Si l’image est en couleur, chaque pixel est représenté par trois octets consécutifs, ou par un octet si elle est en N&B.

Programme à réaliser

Dans ce projet vous devez réaliser un programme de transformation d’images en ascii art.

Le programme final prendra en paramètre un nom de fichier jpeg et les dimensions de l’image ascii de sortie en nombre de caractères par ligne et nombre de lignes. Le programme affiche l’image ascii art sur la sortie standard.

Un exemple d’appel de votre programme est le suivant :

+./jpg2ascii paris.jpg 60 20+
***********%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*********
***********&%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&**********
***********%%%%%%&&&&&&&&&&&aa)Caa&&&&&&&&&&&&&&&&*********
***********&%%%%&&&&&&&aJv)<<<,,<<<)vJaa&&&&&&&&&&*********
***********&%%%&&&&&av<<<<)(v(,)aaCv)<<va&&&&&&&&&*********
***********%%%%&&&&C<<<vaa&&&a<v&&&&&a(<<)aa&&&&&&*********
***********&%%&&&&J<<ua&&&&&&C,)aa&&&&ac<,<C&&&&&&*********
***********&&&&&&J<<v&&&&&&&&C,<aa&&&&&&v<,<a&&&&ao********
***********&%%&&a<<)&&&&&&&&aJ,)aa&&&&aaa(,,Caaa&ao********
***********&%%&&a<<J&&&&&&&&av,)a&&aaaaaao<,va&&&ao********
***********&%%&&J<u&&&&&&&&&a<,<aaaaaaaaaa(,<a&&aao********
***********&%%%%v<<aaaa&&&&av,,,(aaaaaaaoac,,aaaaao********
***********&&&&&h<<uaa&&&aa(,<<v<Caaaaaaaa<,)aoaaao********
***********&**&&&v<)aaa&&aJ,)<,v<<Jaaaaaac,<Caaaaao********
**************&&&a)<vaaaC),)v)<vv<<vaaaaC<,Caaaaaao********
***************aaaa)<(aC<,vaa(+aaa(,<Cac<,voaaaaaao********
***********oo*oaaaaav<,,<vaaa(<aaaaC<<<<(Caaaaaaaao********
**********ooooaaaaaaaaJ)<<<)(<,)()<<<(vaaooaaaaaaao********
**********oooooaaaaaaaaahJvv(((((vcCaaoooooaaaaaaao********
**********oooooaaaaaaaaaaaaaaaaaoaaaaoooooooaaaaaao*******
Tip Vous effectuerez le travail en trois étapes. Les dates limites de chaque étape sont fermes (tout retard entraînera des pénalités, toute étape non rendue entraînera une défaillance au projet, et donc au module). Le projet est individuel.

  • Première étape (à rendre au plus tard le lundi 23 novembre 2015 à 17h) :
    vous devez être capable de charger une image jpeg et de la reécrire dans un autre fichier, pour vérifier que vous lisez correctement les images.

  • Deuxième étape (à rendre au plus tard le vendredi 11 décembre 2015 à 17h) :
    vous devez être capable de transformer l’image rgb en une image hsl. Pour vérifier que votre conversion est correcte, vous trouverez ci après une fonction qui effectue l’opération inverse.

  • Troisième étape (à rendre au plus tard le vendredi 18 décembre à 17h):
    faites la conversion hsl en ascii selon le procédé précédemment décrit.

Chaque étape sera rendue par mail sous forme d'une archive compressée avec gzip contenant l'ensemble des fichiers nécessaires à la compilation et à l'utilisation du projet. L'enseignant à qui vous devrez envoyer votre projet vous sera communiqué ultérieurement (la répartition sera faite en TP).
Tip Pour compiler il faudra utiliser l’option -ljpeg pour l'édition de lien.

Code pour la conversion hsl vers rgb

#define R(buf) *(buf)
#define G(buf) *((buf)+1)
#define B(buf) *((buf)+2)

#define H(buf) *(buf)
#define S(buf) *((buf)+1)
#define L(buf) *((buf)+2)


/* Fonction générique de conversion hsl to rgb */
float hue2rgb(float v1,float v2,float vH)
{
  if (vH<0) vH+=1;
  if (vH>1) vH-=1;
  if ((6*vH)<1) return (v1+(v2-v1)*6*vH);
  if ((2*vH)<1) return (v2);
  if ((3*vH)<2) return (v1+(v2-v1)*((2./3)-vH)*6);
  return(v1);
}

/* Fonction qui convertie le triplet hsl pointé par in en son triplé rvb correspondant
   pointé par out.
   Formule et algo issu de http://www.easyrgb.com/index.php?X=MATH&H=18#text18
*/
void hsl2rgb(float * in, unsigned char * out)
{float x,y;
  if (S(in)==0) {               /* On est en monochrome */
    R(out)=L(in)*255;
    G(out)=L(in)*255;
    B(out)=L(in)*255;
    return;
  }
  if (L(in)<0.5) y = L(in)*(1+S(in));
  else y=(L(in)+S(in))-(S(in)*L(in));
  x=2*L(in)-y;

  R(out)=255*hue2rgb(x,y,H(in)+1./3);
  G(out)=255*hue2rgb(x,y,H(in));
  B(out)=255*hue2rgb(x,y,H(in)-1./3);
}

/* fonction qui prend un buffer image HSL et retourne un buffer image RVB où
   chaque pixel est encodé par trois unsigned char */
unsigned char * convhsl2rgb(float * hsl,unsigned int width, unsigned int height)
{ int i,j;
  unsigned char *rvb=(unsigned char *)malloc(height*width*3*sizeof(char));

  for (i=0;i<height;i++)
    for (j=0;j<width;j++)
      hsl2rgb(hsl+(i*width*3)+j*3,rvb+(i*width*3)+j*3);
  return rvb;
}