surfaces-unies/src/compress.c

220 lines
8.0 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* \file compress.c
* \brief Implémentation de la (dé)compression dimages
*/
#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 à lindex passé en
* argument est éligible à la zone. Si un pixel na pas encore été visité, cela
* veut dire également quil ne fait partie daucun 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 limage `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 Limage contenant les pixels à tester
* \param[in] t_idx Index du pixel à tester
* \param[out] t_zones Liste des zones de limage
*/
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 limage en titérant chaque pixel de limage.
*
* \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,
* cest à dire le nombre de segment quelle 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 limage 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, lensemble 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 dorigine
* \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(&current_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);
}