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