2018-11-12 13:25:24 +00:00
|
|
|
|
/**
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* \file compress.c
|
|
|
|
|
* \brief Implémentation de la (dé)compression d’images
|
2018-11-12 13:25:24 +00:00
|
|
|
|
*/
|
|
|
|
|
|
2018-11-24 15:35:33 +00:00
|
|
|
|
#include "compress.h"
|
2018-11-06 15:08:20 +00:00
|
|
|
|
|
2018-11-27 22:59:04 +00:00
|
|
|
|
/**
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* \var uint32_t tolerance
|
|
|
|
|
* \brief Color tolerance
|
2018-11-27 22:59:04 +00:00
|
|
|
|
*
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* 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.
|
2018-11-27 22:59:04 +00:00
|
|
|
|
*/
|
|
|
|
|
int32_t tolerance;
|
|
|
|
|
|
2018-11-12 13:25:24 +00:00
|
|
|
|
/**
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* 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.
|
2018-11-12 13:25:24 +00:00
|
|
|
|
*
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* \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
|
2018-11-12 13:25:24 +00:00
|
|
|
|
*/
|
2018-11-26 01:28:15 +00:00
|
|
|
|
int32_t sameColor(Pixel *t_pixel, Zone *t_zone) {
|
2018-11-27 22:59:04 +00:00
|
|
|
|
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)
|
2018-12-03 13:54:19 +00:00
|
|
|
|
? 1
|
|
|
|
|
: 0;
|
2018-11-27 22:59:04 +00:00
|
|
|
|
}
|
|
|
|
|
diff_red = (abs((int32_t)t_zone->red - (int32_t)t_pixel->red) * 100) / 255;
|
|
|
|
|
diff_green =
|
2018-12-03 13:54:19 +00:00
|
|
|
|
(abs((int32_t)t_zone->green - (int32_t)t_pixel->green) * 100) / 255;
|
2018-11-27 22:59:04 +00:00
|
|
|
|
diff_blue = (abs((int32_t)t_zone->blue - (int32_t)t_pixel->blue) * 100) / 255;
|
|
|
|
|
return ((diff_red + diff_green + diff_blue) / 3) <= tolerance;
|
2018-11-06 15:08:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-12 13:25:24 +00:00
|
|
|
|
/**
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* 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.
|
2018-11-12 13:25:24 +00:00
|
|
|
|
*
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* \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
|
2018-11-12 13:25:24 +00:00
|
|
|
|
*/
|
2018-11-24 20:58:55 +00:00
|
|
|
|
void addPixelToSelectedZone(Image *t_img, int64_t t_idx, Zone *t_zone) {
|
2018-11-24 20:33:47 +00:00
|
|
|
|
const size_t img_size = darraySize(t_img->pixels);
|
|
|
|
|
Pixel *current_pixel;
|
2018-11-26 01:28:15 +00:00
|
|
|
|
const uint32_t y = (uint32_t)(t_idx / t_img->sizeX);
|
2018-11-24 20:58:55 +00:00
|
|
|
|
int64_t left_limit, right_limit;
|
2018-11-26 01:28:15 +00:00
|
|
|
|
const int64_t xd_limit = (int64_t)t_img->sizeX * (y + 1);
|
2018-12-17 13:50:01 +00:00
|
|
|
|
if (t_idx >= (int64_t)img_size || t_idx < 0) { /* Pixel in range? */
|
2018-11-24 20:33:47 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
current_pixel = darrayGet(t_img->pixels, (size_t)t_idx);
|
2018-12-17 13:50:01 +00:00
|
|
|
|
|
|
|
|
|
/* Pixel already visited or of the right color? */
|
2018-11-24 20:33:47 +00:00
|
|
|
|
if (current_pixel->visited || !sameColor(current_pixel, t_zone)) {
|
2018-11-06 15:08:20 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
2018-11-24 20:33:47 +00:00
|
|
|
|
(*current_pixel).visited = 1;
|
2018-11-27 21:36:00 +00:00
|
|
|
|
|
2018-12-17 13:50:01 +00:00
|
|
|
|
/* right limit */
|
2018-11-24 20:33:47 +00:00
|
|
|
|
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)) {
|
2018-11-06 15:08:20 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
2018-11-24 20:33:47 +00:00
|
|
|
|
current_pixel->visited = 1;
|
2018-11-06 15:08:20 +00:00
|
|
|
|
}
|
2018-12-17 13:50:01 +00:00
|
|
|
|
/* left limit */
|
2018-11-26 01:28:15 +00:00
|
|
|
|
for (left_limit = t_idx; left_limit - (y - 1) * (int64_t)t_img->sizeX >= 0;
|
2018-11-27 14:49:24 +00:00
|
|
|
|
--left_limit) {
|
2018-11-24 20:33:47 +00:00
|
|
|
|
current_pixel = darrayGet(t_img->pixels, (size_t)left_limit);
|
|
|
|
|
if (current_pixel->visited || !sameColor(current_pixel, t_zone)) {
|
2018-11-06 15:08:20 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
2018-11-24 20:33:47 +00:00
|
|
|
|
(*current_pixel).visited = 1;
|
2018-11-06 15:08:20 +00:00
|
|
|
|
}
|
2018-11-27 21:36:00 +00:00
|
|
|
|
|
2018-11-24 20:33:47 +00:00
|
|
|
|
darrayPushBack(t_zone->segments,
|
|
|
|
|
newSegment((uint32_t)right_limit, (uint32_t)left_limit));
|
2018-12-17 13:50:01 +00:00
|
|
|
|
/* for each pixel of the segment, test the pixel above */
|
2018-11-27 14:49:24 +00:00
|
|
|
|
for (; left_limit <= right_limit; ++left_limit) {
|
2018-11-26 01:28:15 +00:00
|
|
|
|
addPixelToSelectedZone(t_img, t_idx + t_img->sizeX, t_zone);
|
2018-11-06 15:08:20 +00:00
|
|
|
|
}
|
2018-12-17 13:50:01 +00:00
|
|
|
|
/* for each pixel of the segment, test the pixel below */
|
2018-11-27 15:59:15 +00:00
|
|
|
|
for (; left_limit <= right_limit; ++left_limit) {
|
|
|
|
|
addPixelToSelectedZone(t_img, t_idx - t_img->sizeX, t_zone);
|
|
|
|
|
}
|
2018-11-06 15:08:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-24 15:35:33 +00:00
|
|
|
|
/**
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* 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.
|
2018-11-24 15:35:33 +00:00
|
|
|
|
*
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* \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
|
2018-11-24 15:35:33 +00:00
|
|
|
|
*/
|
2018-11-24 21:41:29 +00:00
|
|
|
|
void chooseZoneForPixel(Image *t_img, int64_t t_idx, darray *t_zones) {
|
2018-11-24 20:33:47 +00:00
|
|
|
|
Zone *current_zone;
|
|
|
|
|
Pixel *pixel;
|
2018-11-06 15:08:20 +00:00
|
|
|
|
size_t i;
|
2018-11-24 20:33:47 +00:00
|
|
|
|
pixel = darrayGet(t_img->pixels, (size_t)t_idx);
|
2018-11-22 13:05:11 +00:00
|
|
|
|
if (pixel->visited) {
|
2018-11-06 15:08:20 +00:00
|
|
|
|
return;
|
2018-11-22 13:05:11 +00:00
|
|
|
|
}
|
2018-11-24 21:41:29 +00:00
|
|
|
|
for (i = 0; i < darraySize(t_zones); ++i) {
|
|
|
|
|
current_zone = darrayGet(t_zones, i);
|
2018-11-06 15:08:20 +00:00
|
|
|
|
if (sameColor(pixel, current_zone)) {
|
|
|
|
|
addPixelToSelectedZone(t_img, t_idx, current_zone);
|
2018-11-22 13:05:11 +00:00
|
|
|
|
return;
|
2018-11-06 15:08:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-11-26 01:28:15 +00:00
|
|
|
|
current_zone = newZone(pixel->red, pixel->green, pixel->blue);
|
2018-11-24 21:41:29 +00:00
|
|
|
|
darrayPushBack(t_zones, current_zone);
|
2018-11-22 13:05:11 +00:00
|
|
|
|
addPixelToSelectedZone(t_img, t_idx, current_zone);
|
2018-11-06 15:08:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-24 15:35:33 +00:00
|
|
|
|
/**
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* Génère les zones de l’image en titérant chaque pixel de l’image.
|
2018-11-24 15:35:33 +00:00
|
|
|
|
*
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* \param t_img Image à convertir en zones
|
|
|
|
|
* \return Pointeur vers un \ref darray de structures \ref Zone
|
2018-11-24 15:35:33 +00:00
|
|
|
|
*/
|
2018-11-24 20:33:47 +00:00
|
|
|
|
darray *imgToZones(Image *t_img) {
|
2018-11-21 09:46:01 +00:00
|
|
|
|
darray *zones;
|
2018-11-06 15:08:20 +00:00
|
|
|
|
const size_t nb_pixels = darraySize(t_img->pixels);
|
2018-11-24 20:58:55 +00:00
|
|
|
|
int64_t i;
|
2018-11-06 15:08:20 +00:00
|
|
|
|
zones = darrayNew(sizeof(Zone));
|
2018-11-24 20:58:55 +00:00
|
|
|
|
for (i = 0; i < (int64_t)nb_pixels; ++i) {
|
2018-11-06 15:08:20 +00:00
|
|
|
|
chooseZoneForPixel(t_img, i, zones);
|
|
|
|
|
}
|
|
|
|
|
return zones;
|
|
|
|
|
}
|
2018-11-07 00:03:55 +00:00
|
|
|
|
|
2018-11-26 01:28:15 +00:00
|
|
|
|
/**
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* 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.
|
2018-11-26 01:28:15 +00:00
|
|
|
|
*
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* \param[out] t_output Fichier de sortie
|
|
|
|
|
* \param[in] t_segments Segments à écrire dans \p t_output
|
2018-11-26 01:28:15 +00:00
|
|
|
|
*/
|
|
|
|
|
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);
|
2018-11-27 09:38:11 +00:00
|
|
|
|
fwrite(&segment->left_limit, sizeof(Segment), 1, t_output);
|
2018-11-26 01:28:15 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* É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.
|
2018-11-26 01:28:15 +00:00
|
|
|
|
*
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* \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
|
2018-11-26 01:28:15 +00:00
|
|
|
|
*/
|
2018-11-24 23:29:35 +00:00
|
|
|
|
void write_compressed_file(Image *t_img, FILE *t_output, darray *t_zones) {
|
2018-11-26 01:28:15 +00:00
|
|
|
|
uint64_t i, nb_zones = darraySize(t_zones);
|
2018-11-24 23:29:35 +00:00
|
|
|
|
Zone *current_zone;
|
2018-11-26 01:28:15 +00:00
|
|
|
|
fwrite(&t_img->sizeX, sizeof(t_img->sizeX), 2, t_output);
|
|
|
|
|
fwrite(&nb_zones, sizeof(nb_zones), 1, t_output);
|
2018-11-24 23:29:35 +00:00
|
|
|
|
for (i = 0; i < darraySize(t_zones); ++i) {
|
|
|
|
|
current_zone = darrayGet(t_zones, i);
|
2018-11-26 01:28:15 +00:00
|
|
|
|
fwrite(¤t_zone->red, sizeof(current_zone->red) * 3, 1, t_output);
|
|
|
|
|
write_segments(t_output, current_zone->segments);
|
2018-11-24 23:29:35 +00:00
|
|
|
|
darrayDelete(current_zone->segments);
|
|
|
|
|
}
|
2018-11-26 01:28:15 +00:00
|
|
|
|
darrayDelete(t_zones);
|
2018-11-24 23:29:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-24 15:35:33 +00:00
|
|
|
|
/**
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* Convertit une image en zones puis écrit ces zones dans un fichier,
|
|
|
|
|
* compressant ainsi l'image passée en argument.
|
2018-11-24 15:35:33 +00:00
|
|
|
|
*
|
2018-12-03 13:54:19 +00:00
|
|
|
|
* \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
|
2018-11-24 15:35:33 +00:00
|
|
|
|
*/
|
2018-11-27 22:59:04 +00:00
|
|
|
|
void compress(const char *t_input_file, const char *t_output_file,
|
|
|
|
|
int32_t t_tolerance) {
|
2018-11-23 09:31:47 +00:00
|
|
|
|
Image *img;
|
2018-11-21 09:46:01 +00:00
|
|
|
|
darray *zones;
|
2018-11-24 23:29:35 +00:00
|
|
|
|
FILE *output_file;
|
2018-11-26 01:28:15 +00:00
|
|
|
|
if (!t_output_file) {
|
|
|
|
|
t_output_file = DEFAULT_COMPRESSED_NAME;
|
|
|
|
|
}
|
2018-11-27 22:59:04 +00:00
|
|
|
|
tolerance = t_tolerance;
|
2018-11-07 00:03:55 +00:00
|
|
|
|
img = newImage();
|
2018-11-24 15:35:33 +00:00
|
|
|
|
imageLoadPPM(t_input_file, img);
|
2018-11-24 23:29:35 +00:00
|
|
|
|
output_file = get_file(t_output_file, "wb");
|
2018-11-07 00:03:55 +00:00
|
|
|
|
zones = imgToZones(img);
|
2018-11-24 23:29:35 +00:00
|
|
|
|
write_compressed_file(img, output_file, zones);
|
2018-11-07 00:03:55 +00:00
|
|
|
|
deleteImage(img);
|
2018-11-24 23:29:35 +00:00
|
|
|
|
fclose(output_file);
|
2018-11-07 00:03:55 +00:00
|
|
|
|
}
|