compress.c 7.96 KB
Newer Older
1
/**
2 3
 *	 \file compress.c
 *	 \brief Implémentation de la (dé)compression d’images
4 5
 */

6
#include "compress.h"
7

8
/**
9 10
 *	\var uint32_t tolerance
 *	\brief Color tolerance
11
 *
12 13 14
 *	Cette variable est la valeur du pourcentage de tolérance couleur lors de la
 *	création de nouvelles zones. Cette variable contient une valeur située entre
 *	0 et 100 inclus.
15 16 17
 */
int32_t tolerance;

18
/**
19 20 21 22
 *	Cette fonction permet d’évaluer si le pixel passé en argument est éligible à
 *	la zone passée également en argument. Si la \ref tolerance a pour valeur 0,
 *	alors les couleurs doivent être strictements identiques. Sinon, leur
 *	différence doit être inférieure à la tolérance de couleur.
23
 *
24 25 26
 *	\param[in] t_pixel Pointeur vers le pixel dont l’éligibilité est testée
 *	\param[in] t_zone Zone à laquelle le pixel est éligible ou non
 *	\return Valeur booléenne, `1` si le pixel est éligible, `0` sinon
27
 */
28
int32_t sameColor(Pixel *t_pixel, Zone *t_zone) {
29 30 31 32
  int diff_red, diff_green, diff_blue;
  if (tolerance == 0) {
    return (t_pixel->red == t_zone->red && t_pixel->green == t_zone->green &&
            t_pixel->blue == t_zone->blue)
33 34
      ? 1
      : 0;
35 36 37
  }
  diff_red = (abs((int32_t)t_zone->red - (int32_t)t_pixel->red) * 100) / 255;
  diff_green =
38
    (abs((int32_t)t_zone->green - (int32_t)t_pixel->green) * 100) / 255;
39 40
  diff_blue = (abs((int32_t)t_zone->blue - (int32_t)t_pixel->blue) * 100) / 255;
  return ((diff_red + diff_green + diff_blue) / 3) <= tolerance;
41 42
}

43
/**
44 45 46 47 48 49 50
 *	Ajoute un pixel à la zone passé en argument si le pixel à l’index passé en
 *	argument est éligible à la zone. Si un pixel n’a pas encore été visité, cela
 *	veut dire également qu’il ne fait partie d’aucun segment, il sera donc
 *	ajouté à un nouveau segment auquel seront rajoutés tous les pixels connexes
 *	éligibles à la zone. Ensuite, le segment est ajouté à la zone, et la
 *	fonction actuelle est appelée sur tous les pixels supérieurs et inférieurs
 *	aux pixels du segment.
51
 *
52 53 54
 *	\param[in] t_img Image contenant les pixels explorés
 *	\param[in] t_idx Index du pixel actuel dans l’image `t_img`
 *	\param[out] t_zone Zone à laquelle sera potentiellement ajouté le pixel
55
 */
56
void addPixelToSelectedZone(Image *t_img, int64_t t_idx, Zone *t_zone) {
57 58
  const size_t img_size = darraySize(t_img->pixels);
  Pixel *current_pixel;
59
  const uint32_t y = (uint32_t)(t_idx / t_img->sizeX);
60
  int64_t left_limit, right_limit;
61
  const int64_t xd_limit = (int64_t)t_img->sizeX * (y + 1);
62
  if (t_idx >= (int64_t)img_size || t_idx < 0) { /* Pixel in range? */
63 64 65
    return;
  }
  current_pixel = darrayGet(t_img->pixels, (size_t)t_idx);
66 67

  /* Pixel already visited or of the right color? */
68
  if (current_pixel->visited || !sameColor(current_pixel, t_zone)) {
69 70
    return;
  }
71
  (*current_pixel).visited = 1;
72

73
  /* right limit */
74 75 76
  for (right_limit = t_idx; right_limit < xd_limit; ++right_limit) {
    current_pixel = darrayGet(t_img->pixels, (size_t)right_limit);
    if (!sameColor(current_pixel, t_zone)) {
77 78
      break;
    }
79
    current_pixel->visited = 1;
80
  }
81
  /* left limit */
82
  for (left_limit = t_idx; left_limit - (y - 1) * (int64_t)t_img->sizeX >= 0;
83
       --left_limit) {
84 85
    current_pixel = darrayGet(t_img->pixels, (size_t)left_limit);
    if (current_pixel->visited || !sameColor(current_pixel, t_zone)) {
86 87
      break;
    }
88
    (*current_pixel).visited = 1;
89
  }
90

91 92
  darrayPushBack(t_zone->segments,
                 newSegment((uint32_t)right_limit, (uint32_t)left_limit));
93
  /* for each pixel of the segment, test the pixel above */
94
  for (; left_limit <= right_limit; ++left_limit) {
95
    addPixelToSelectedZone(t_img, t_idx + t_img->sizeX, t_zone);
96
  }
97
  /* for each pixel of the segment, test the pixel below */
98 99 100
  for (; left_limit <= right_limit; ++left_limit) {
    addPixelToSelectedZone(t_img, t_idx - t_img->sizeX, t_zone);
  }
101 102
}

103
/**
104 105 106
 *	Sélectionne la zone correspondant à la couleur du pixel. Si aucune zone
 *	existante ne correspond, une nouvelle est créée et est ajoutée à l'image.
 *	Chaque pixel est itéré, et ignoré si le pixel a déjà été visité auparavant.
107
 *
108 109 110
 *	\param[out] t_img L’image contenant les pixels à tester
 *	\param[in] t_idx Index du pixel à tester
 *	\param[out] t_zones Liste des zones de l’image
111
 */
112
void chooseZoneForPixel(Image *t_img, int64_t t_idx, darray *t_zones) {
113 114
  Zone *current_zone;
  Pixel *pixel;
115
  size_t i;
116
  pixel = darrayGet(t_img->pixels, (size_t)t_idx);
117
  if (pixel->visited) {
118
    return;
119
  }
120 121
  for (i = 0; i < darraySize(t_zones); ++i) {
    current_zone = darrayGet(t_zones, i);
122 123
    if (sameColor(pixel, current_zone)) {
      addPixelToSelectedZone(t_img, t_idx, current_zone);
124
      return;
125 126
    }
  }
127
  current_zone = newZone(pixel->red, pixel->green, pixel->blue);
128
  darrayPushBack(t_zones, current_zone);
129
  addPixelToSelectedZone(t_img, t_idx, current_zone);
130 131
}

132
/**
133
 *	Génère les zones de l’image en titérant chaque pixel de l’image.
134
 *
135 136
 *	\param t_img Image à convertir en zones
 *	\return Pointeur vers un \ref darray de structures \ref Zone
137
 */
138
darray *imgToZones(Image *t_img) {
139
  darray *zones;
140
  const size_t nb_pixels = darraySize(t_img->pixels);
141
  int64_t i;
142
  zones = darrayNew(sizeof(Zone));
143
  for (i = 0; i < (int64_t)nb_pixels; ++i) {
144 145 146 147
    chooseZoneForPixel(t_img, i, zones);
  }
  return zones;
}
148

149
/**
150 151 152
 *	Cette fonction écrit dans \p t_output la taille en `uint64_t` de la zone,
 *	c’est à dire le nombre de segment qu’elle contient, puis écrit
 *	individuellement chaque segment dans \p t_output.
153
 *
154 155
 *	\param[out] t_output Fichier de sortie
 *	\param[in] t_segments Segments à écrire dans \p t_output
156 157 158 159 160 161 162 163
 */
void write_segments(FILE *t_output, darray *t_segments) {
  uint64_t nb_segments, j;
  Segment *segment;
  nb_segments = darraySize(t_segments);
  fwrite(&nb_segments, sizeof(nb_segments), 1, t_output);
  for (j = 0; j < darraySize(t_segments); ++j) {
    segment = darrayGet(t_segments, j);
164
    fwrite(&segment->left_limit, sizeof(Segment), 1, t_output);
165 166 167 168
  }
}

/**
169 170 171 172 173 174 175
 *	Écrit la taille de l’image en abscisse et ordonnées, les deux sous forme de
 *	`uint64_t` puis le nombre de zones sous forme de `uint64_t`. Puis, pour
 *	chaque zone son code couleur composé de trois `uint8_t` successifs
 *	représentant ses couleurs rouge, vert et bleu sont écrit dans le fichier de
 *	sortie \p t_output. Après chaque écriture de zone, l’ensemble des segments
 *	de la zone est libéré de la mémoire. Une fois toutes les zones écrites dans
 *	le fichier de sortie, \p t_zones et libéré de la mémoire.
176
 *
177 178 179
 *	\param[in] t_img \ref Image contenant les dimensions du fichier d’origine
 *	\param[out] t_output Fichier où sont écrites les données compressées
 *	\param[in] t_zones Tableau des \ref Zone à écrire puis libérer
180
 */
181
void write_compressed_file(Image *t_img, FILE *t_output, darray *t_zones) {
182
  uint64_t i, nb_zones = darraySize(t_zones);
183
  Zone *current_zone;
184 185
  fwrite(&t_img->sizeX, sizeof(t_img->sizeX), 2, t_output);
  fwrite(&nb_zones, sizeof(nb_zones), 1, t_output);
186 187
  for (i = 0; i < darraySize(t_zones); ++i) {
    current_zone = darrayGet(t_zones, i);
188 189
    fwrite(&current_zone->red, sizeof(current_zone->red) * 3, 1, t_output);
    write_segments(t_output, current_zone->segments);
190 191
    darrayDelete(current_zone->segments);
  }
192
  darrayDelete(t_zones);
193 194
}

195
/**
196 197
 *	Convertit une image en zones puis écrit ces zones dans un fichier,
 *	compressant ainsi l'image passée en argument.
198
 *
199 200 201
 *	\param[in] t_input_file Nom/chemin du fichier `.ppm` d'entrée
 *	\param[in] t_output_file Nom/chemin du fichier `.su` de sortie
 *	\param[in] t_tolerance Pourcentage de tolérance de couleur
202
 */
203 204
void compress(const char *t_input_file, const char *t_output_file,
              int32_t t_tolerance) {
205
  Image *img;
206
  darray *zones;
207
  FILE *output_file;
208 209 210
  if (!t_output_file) {
    t_output_file = DEFAULT_COMPRESSED_NAME;
  }
211
  tolerance = t_tolerance;
212
  img = newImage();
213
  imageLoadPPM(t_input_file, img);
214
  output_file = get_file(t_output_file, "wb");
215
  zones = imgToZones(img);
216
  write_compressed_file(img, output_file, zones);
217
  deleteImage(img);
218
  fclose(output_file);
219
}