123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514 |
- #include <iostream>
- #include <fstream>
- #include <vector>
- #include <cmath>
- #include <iomanip>
- #include <filesystem>
- class BMP2Gcode
- {
- public:
- BMP2Gcode();
- void traitement(std::string nom_fichier);
- int fichier_configuration(std::string conf);
- private:
- int entete();
- int donnees();
- int sortie();
- unsigned int conversion(unsigned int valeur, unsigned int min, unsigned int max);
- unsigned int consecutif(unsigned int indice, unsigned int min, unsigned int max, unsigned int puissance, int sens);
- unsigned int BMP_largeur, BMP_hauteur, BMP_offset, BMP_profondeur, BMP_taille;
- double conf_taille_laser, conf_puissance_min, conf_puissance_max, conf_vitesse, conf_vitesse_max, conf_taille_image, conf_taille_pixel;
- std::vector<unsigned int> tab_donnees;
- std::string nom_fichier_bmp;
- };
- BMP2Gcode::BMP2Gcode()
- :BMP_largeur(0), BMP_hauteur(0), BMP_offset(0), BMP_profondeur(0), BMP_taille(0),
- conf_taille_laser(-1), conf_puissance_min(-1), conf_puissance_max(-1),
- conf_vitesse(-1), conf_vitesse_max(-1),
- conf_taille_image(-1), conf_taille_pixel(-1)
- {}
- void BMP2Gcode::traitement(std::string nom_fichier)
- {
- //convertion du fichier et redimensionnement du fichier
- int nbr_pixel_x = conf_taille_image / conf_taille_pixel;
- std::string commande("convert " + nom_fichier + " -resize " + std::to_string(nbr_pixel_x) + " " + nom_fichier + ".bmp");
- system(commande.c_str());
-
- nom_fichier_bmp = nom_fichier + ".bmp";
- if(entete() == 0)
- {
- if(donnees() == 0)
- {sortie();}
- else
- {std::cout << "une erreur s'est produite" << std::endl;}
- }
- else
- {std::cout << "une erreur s'est produite" << std::endl;}
- }
- int BMP2Gcode::entete()
- {
- //ouverture du fichier
- std::ifstream fichier(nom_fichier_bmp, std::ifstream::binary);
- //test de l'ouverture du fichier
- if(!fichier)
- {
- std::cerr << "impossible d'ouvrir le fichier" << std::endl;
- return 1;
- }
- /*lecture de l'en tête de 54 octets composé comme suit :
- -2 octets [0-1] pour la signature
- -4 octets [2-5] pour la taille du fichier en octets
- -4 octets [6-9] de champ réservé
- -4 octets [10-13] pour l'offset
- -4 octets [14-17] pour la taille de l'en-tête de l'image (28 octets pour windows)
- -4 octets [18-21] pour la largeur de l'image
- -4 octets [22-25] pour la hauteur de l'image
- -2 octets [26-27] pour le nombre de plans (valeur toujours à 1)
- -2 octets [28-29] pour la profondeur de l'encodage (nbr de bit pour la couleur)
- -4 octets [30-33] pour la métode de compression (0 non compressé, 1 RLE 8bits/pixel, 2 RLE 4bits/pixel, 3 bitfields)
- -4 octets [35-38] pour la taille de l'image
- -4 octets [39-42] pour la résolution horizontale
- -4 octets [43-46] pour la résolution verticale
- -4 octets [47-50] pour la palette de couleur
- -4 octets [50-53] pour le nombre de couleurs importantes.
- */
- char *entete = new char [54];
- fichier.read(entete, 54);
- if(entete[0] == 'B')
- {
- if(entete[1] == 'M')
- {
- std::cout << "Bitmap windows" << std::endl;
- }
- }
- /*Pour récupérer les informations on va utiliser l'astuce suivante :
- pour la valeur commençant à entete[indice] on va commencer par prendre l'adresse :
- &entete[indice]
- puis on va caster ça dans le type qui correspond bien, int * si c'est sur 4 octets,
- short * si c'est sur 2 octets.
- (int *)&entete[indice] correspond donc à un pointeur de type int *, il suffit alors
- de prendre sa valeur en faisant : *(int *)entete[indice]
- */
- BMP_offset = *(int*)&entete[10];
- BMP_largeur = *(int*)&entete[18];
- BMP_hauteur = *(int*)&entete[22];
- BMP_profondeur = *(short*)&entete[28];
-
- std::cout << "compression : " << *(int*)&entete[30] << std::endl;
- std::cout << "offset BMP : " << BMP_offset << std::endl;
- std::cout << "largeur BMP : " << BMP_largeur << std::endl;
- std::cout << "hauteur BMP : " << BMP_hauteur << std::endl;
- std::cout << "profondeur BMP : " << BMP_profondeur << std::endl;
-
- delete[] entete;
- fichier.close();
- return 0;
- }
- int BMP2Gcode::donnees()
- {
- //ouverture du fichier
- std::ifstream fichier(nom_fichier_bmp, std::ifstream::binary);
- //test de l'ouverture du fichier
- if(!fichier)
- {
- std::cerr << "impossible d'ouvrir le fichier" << std::endl;
- return 1;
- }
- if(BMP_offset == 0)
- {entete();}
- /* /!\ATTENTION/!\
- Contrairement à la plupart des formats d'images, les pixels de l'image sont codés en partant de la ligne inférieure de l'image. Chaque ligne (codée de gauche à droite) doit toujours occuper un nombre d'octets multiple de 4, excepté si l'image est compressée. Si la ligne ne possède pas un nombre d'octets multiple de 4, on ajoute FF, 00FF, ou 0000FF à la fin de chaque ligne
-
- Source : https://fr.wikipedia.org/wiki/Windows_bitmap#Disposition_des_donn%C3%A9es_de_l'image
-
- Il est donc nécessaire pour lire correctement les données de prévoir une limite qui est un multiple de 4
- mais également de sauter ces éventuels informations
- */
- int reste = (BMP_largeur * BMP_profondeur/8) % 4, longueur_ligne = BMP_largeur * BMP_profondeur/8;
-
- if(reste != 0)
- {
- //Si la longueur d'une ligne n'est un multiple de 4
- //il suffit alors d'ajouter à la ligne 4 - reste
- longueur_ligne += (4 - reste);
- }
- //Maintenant qu'on connait la taille réelle d'une ligne on peut calculer la limite
- int limite = longueur_ligne * BMP_hauteur + BMP_offset;
- char *donnees = new char [ limite ];
- fichier.read(donnees, limite);
- int depart = BMP_offset;
- for(int i = 0 ; i < BMP_hauteur ; i++)
- {
- //on récupère une ligne
- for(int j = depart ; j < depart + BMP_largeur * BMP_profondeur/8; j += BMP_profondeur/8)
- {
- if(donnees[j] < 0)
- {tab_donnees.push_back(256 + (int)donnees[j]);}
- else
- {tab_donnees.push_back((int)donnees[j]);}
- }
- depart += longueur_ligne;
- }
- delete[] donnees;
- fichier.close();
- return 0;
- }
- int BMP2Gcode::sortie()
- {
- std::string nom_fichier_gcode;
- nom_fichier_gcode = nom_fichier_bmp + ".gcode";
- std::ofstream fichier_sortie;
- fichier_sortie.open (nom_fichier_gcode);
- if(!fichier_sortie)
- {
- std::cerr << "impossible d'ouvrir le fichier de sortie" << std::endl;
- return -1;
- }
- /****************************CONDITIONS INITIALES****************************/
- int sens = 1, nbr_passage = 1, ligne = 0, colonne = 0;
- std::cout << "***SORTIE***" << std::endl;
- std::cout << "BMP_largeur = " << BMP_largeur << " mm" << std::endl;
- std::cout << "largeur = " << BMP_largeur * conf_taille_pixel << " mm" << std::endl;
- std::cout << "hauteur = " << BMP_hauteur * conf_taille_pixel << " mm" << std::endl;
- std::cout << "taille pixel = " << conf_taille_pixel << " mm" << std::endl;
- //On passe en mode relatif
- fichier_sortie << "G91" << std::endl;
- //on s'assure que le laser est éteint
- fichier_sortie << "M106 P1 S0" << std::endl;
- //on défini la vitesse
- fichier_sortie << "G1 F" << conf_vitesse << std::endl;
-
- //on parcourt toutes les lignes
- while(ligne < BMP_hauteur)
- {
- //pour graver une ligne on fera des aller-retour tant que :
- do
- {
- int indice = ligne * BMP_largeur + colonne;
- unsigned int puissance = 0, indice_min = 0, indice_max = 0, nbr = 1;
-
- puissance = conversion(255 - tab_donnees[indice], conf_puissance_min, conf_puissance_max);
- indice_min = ligne * BMP_largeur;
- indice_max = indice_min + BMP_largeur - 1;
- //on détermine le nombre de pixels successifs pour lesquels la puissance du laser sera la même
- nbr = consecutif(indice, indice_min, indice_max, puissance, sens);
-
- //pour accélérer la gravure, on va augmenter la vitesse au dessus des blancs
- //c'est à dire quand la puissances du laser == 0
- if(puissance == 0)
- {fichier_sortie << "G1 F" << conf_vitesse_max << std::endl;}
-
- fichier_sortie << "M106 P1 S" << puissance << std::endl;
- //on se déplace selon le sens
- fichier_sortie << "G1 X" << sens * conf_taille_pixel * nbr << std::endl;
- colonne += sens * nbr;
-
- //ne pas oublier de rétablir la vitesse après les blancs
- if(puissance == 0)
- {fichier_sortie << "G1 F" << conf_vitesse << std::endl;}
- }
- while(colonne < BMP_largeur && colonne >= 0);
-
- /*arrivé ici on vient de tracer une ligne et le paramètre colonne est hors limite, il faut donc le rectifier.
- Il est peut être également nécessaire de faire un retour cela va dépendre du rapport
- entre la taille du pixel et la taille du laser*/
-
- if(colonne < 0)
- {colonne = 0;}
- if(colonne >= BMP_largeur)
- {colonne = BMP_largeur - 1;}
-
- if(conf_taille_pixel > conf_taille_laser * nbr_passage)
- {
- /*si on a pas fait un nombre de passage suffisent pour égaler la taille du pixel
- alors on repart dans l'autre sens en prennant soin de décaler la colonne
- et on recommence*/
- nbr_passage ++;
- }
- else
- {
- /*si le nombre de passage permet d'égaler la taille du pixel
- alors on change de ligne et on recommence*/
- ligne ++;
- nbr_passage = 1;
- }
-
- /*Dans les 2 cas il est nécessaire de se déplacer sur l'axe Y
- et de changer de sens*/
- fichier_sortie << "G1 Y" << conf_taille_laser << std::endl;
- sens *= -1;
- }
- //il ne faut pas oublier d'éteindre le laser à la fini
- fichier_sortie << "M106 P1 S0" << std::endl;
- fichier_sortie.close();
- return 0;
- }
- unsigned int BMP2Gcode::conversion(unsigned int valeur, unsigned int min, unsigned int max)
- {
- /*pour une valeur de 0 la puissance du laser doit être égale à min
- *pour une valeur de 255 la puissance du laser doit être égal à max
- *pour trouver la valeur entre les deux on utilise une fonction affine
- y = ax + b
- pour x = 0 <=> y = b = min
- pour x = 255 <=> y = 255*a+min = max
- <=> a = (max - min)/255
- */
-
- return valeur * (max - min)/255 + min;
- }
- unsigned int BMP2Gcode::consecutif(unsigned int indice, unsigned int min, unsigned int max, unsigned int puissance, int sens)
- {
- /*Cette fonction va déterminer le nombre de pixel consécutif
- avec la même couleur que le premier pixel, dans un interval donné
-
- du fait de la calibration il se peut que des teintes de gris proches
- soient associées à la même puissance laser,
- il est donc nécessaire de comparer les puissance et non les données des pixel*/
-
- unsigned int nbr = 0;
-
- while(indice >= min && indice <= max)
- {
- if(puissance == conversion(255 - tab_donnees[indice], conf_puissance_min, conf_puissance_max))
- {nbr ++;}
- else
- {return nbr;}
-
- indice += sens;
- }
- return nbr;
- }
- int BMP2Gcode::fichier_configuration(std::string conf)
- {
- int nbr_parametres = 0;
- //On regarde si le fichier de configuration existe déjà
- std::ifstream fichier_conf(conf);
- //s'il n'existe pas
- if(!fichier_conf)
- {
- //alors on le crée
- std::ofstream fichier_conf;
- fichier_conf.open(conf);
- fichier_conf << "taille_laser(mm) 0.2" << std::endl;
- fichier_conf << "puissance_min 0" << std::endl;
- fichier_conf << "puissance_max 255" << std::endl;
- fichier_conf << "vitesse(mm/min) 1300" << std::endl;
- fichier_conf << "vitesse_max(mm/min) 8000" << std::endl;
- fichier_conf << "taille_image_x(mm) 150" << std::endl;
- fichier_conf << "taille_pixel(mm) 0.4" << std::endl;
- std::cout << "*************************************************" <<std::endl;
- std::cout << "ATTENTION, un fichier de configuration a été crée" <<std::endl;
- std::cout << "Il ne contient probablement pas les bonnes valeurs" <<std::endl;
- std::cout << "Pour les connaitre il est nécessaire d'utiliser" <<std::endl;
- std::cout << "les scripts d'étalonnages" <<std::endl;
- std::cout << "*************************************************" <<std::endl;
- conf_taille_laser = 0.2;
- conf_puissance_min = 0;
- conf_puissance_max = 255;
- conf_vitesse = 1300;
- conf_vitesse_max = 8000;
- conf_taille_image = 150;
- conf_taille_pixel = 0.4;
- }
- else
- {
- //s'il existe on récupère les valeurs
- std::string clef;
- double valeur;
- while(fichier_conf >> clef >> valeur)
- {
- if(clef == "taille_laser(mm)")
- {conf_taille_laser = valeur;}
- if(clef == "puissance_min")
- {conf_puissance_min = valeur;}
- if(clef == "puissance_max")
- {conf_puissance_max = valeur;}
- if(clef == "vitesse(mm/min)")
- {conf_vitesse = valeur;}
- if(clef == "vitesse_max(mm/min)")
- {conf_vitesse_max = valeur;}
- if(clef == "taille_image_x(mm)")
- {conf_taille_image = valeur;}
- if(clef == "taille_pixel(mm)")
- {conf_taille_pixel = valeur;}
- }
-
- //vérification des paramètres
- if(conf_taille_laser == -1)
- {
- std::cout << "ATTENTION ! La taille du laser n'a pas été fournie" << std::endl;
- return 3;
- }
- if(conf_puissance_min == -1)
- {
- std::cout << "ATTENTION ! La puissance minimale du laser n'a pas été fournie" << std::endl;
- return 3;
- }
-
- if(conf_puissance_max == -1)
- {
- std::cout << "ATTENTION ! La puissance maximale du laser n'a pas été fournie" << std::endl;
- return 3;
- }
- if(conf_vitesse == -1)
- {
- std::cout << "ATTENTION ! La vitesse du laser n'a pas été fournie" << std::endl;
- return 3;
- }
- if(conf_vitesse_max == -1)
- {
- std::cout << "ATTENTION ! La vitesse maximale du laser n'a pas été fournie" << std::endl;
- return 3;
- }
-
- if(conf_taille_image == -1)
- {
- std::cout << "ATTENTION ! La taille de l'image n'a pas été fournie" << std::endl;
- return 3;
- }
- if(conf_taille_pixel == -1)
- {
- std::cout << "ATTENTION ! La taille du pixel n'a pas été fournie" << std::endl;
- return 3;
- }
- std::cout << "***CONFIGURATION***" << std::endl;
- std::cout << "taille_laser = " << conf_taille_laser << " mm" << std::endl;
- std::cout << "puissance_min = " << conf_puissance_min << " PWM 0-255" << std::endl;
- std::cout << "puissance_max = " << conf_puissance_max << " PWM 0-255" << std::endl;
- std::cout << "vitesse = " << conf_vitesse << " mm/min" << std::endl;
- std::cout << "vitesse_max = " << conf_vitesse_max << " mm/min" << std::endl;
- std::cout << "taille_image = " << conf_taille_image << " mm" << std::endl;
- std::cout << "taille_pixel = " << conf_taille_pixel << " mm" << std::endl;
- }
- fichier_conf.close();
- return 0;
- }
- void aide()
- {
- std::cout << "Ce programme permet de générer une fichier gcode à partir d'une image." << std::endl;
- std::cout << "Pour fonctionner il nécessite imagemagik afin de convertir et redimensionner les images." << std::endl;
- std::cout << "Les options :" << std::endl;
- std::cout << "-h\t--help\taffiche cette aide" << std::endl;
- std::cout << "-c\t\tpour préciser le fichier de configuration" << std::endl;
- std::cout << "-i\t\tpour préciser l'image à traiter" << std::endl;
- }
- int main(int argc, char* argv[])
- {
- //Il est nécessaire d'avoir au moins 3 arguments
- if(argc >= 3)
- {
- //On cherche la position des arguments
- int position_c = -1, position_i = -1;
-
- for(int i = 0 ; i < argc ; i++)
- {
- if(std::string(argv[i]) == "-c")
- {position_c = i;}
-
- if(std::string(argv[i]) == "-i")
- {position_i = i;}
- }
- //On vérifie que les positions sont cohérentes
- if(position_c == argc || position_i == argc || abs(position_c - position_i) < 2)
- {
- std::cout << "argument manquant\n" << std::endl;
- aide();
- return 1;
- }
-
- //si on arrive ici c'est que des arguments ont été fournis
- //On va donc les récupérer
- std::string conf, fichier;
-
- if(position_c > 0)
- {conf = argv[position_c + 1];}
-
- if(position_i > 0)
- {fichier = argv[position_i + 1];}
-
- std::cout << "fichier image : " << fichier << std::endl;
-
- //on vérifie qu'il y a bien un fichier image
- //s'il n'existe pas
- std::ifstream fichier_image(fichier);
- if (!fichier_image)
- {
- // read away
- std::cout << "Le fichier image n'existe pas" << std::endl;
- return 2;
- }
- BMP2Gcode image;
-
- if(image.fichier_configuration(conf) != 0)
- {return 3;}
- image.traitement(fichier);
- }
- else
- {aide();}
- return 0;
- }
|