bmp2gcode.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. #include <iostream>
  2. #include <fstream>
  3. #include <vector>
  4. #include <cmath>
  5. #include <iomanip>
  6. #include <filesystem>
  7. class BMP2Gcode
  8. {
  9. public:
  10. BMP2Gcode();
  11. void traitement(std::string &nom_fichier);
  12. int fichier_configuration(std::string &conf);
  13. void position_depart(std::string &position);
  14. private:
  15. int entete();
  16. int donnees();
  17. int sortie();
  18. unsigned int conversion(unsigned int valeur, unsigned int min, unsigned int max);
  19. unsigned int consecutif(unsigned int indice, unsigned int min, unsigned int max, unsigned int puissance, int sens);
  20. unsigned int BMP_largeur, BMP_hauteur, BMP_offset, BMP_profondeur, BMP_taille;
  21. double conf_taille_laser, conf_puissance_min, conf_puissance_max, conf_vitesse, conf_vitesse_max, conf_taille_image, conf_taille_pixel;
  22. std::vector<unsigned int> tab_donnees;
  23. std::string nom_fichier_bmp, pos_depart;
  24. };
  25. BMP2Gcode::BMP2Gcode()
  26. :BMP_largeur(0), BMP_hauteur(0), BMP_offset(0), BMP_profondeur(0), BMP_taille(0),
  27. conf_taille_laser(-1), conf_puissance_min(-1), conf_puissance_max(-1),
  28. conf_vitesse(-1), conf_vitesse_max(-1),
  29. conf_taille_image(-1), conf_taille_pixel(-1)
  30. {}
  31. void BMP2Gcode::traitement(std::string &nom_fichier)
  32. {
  33. //vérification de l'existance du fichier image
  34. std::ifstream fichier_image(nom_fichier);
  35. //s'il n'existe pas
  36. if(!fichier_image)
  37. {
  38. std::cout << "ATTENTION ! le fichier image n'existe pas" << std::endl;
  39. }
  40. else
  41. {
  42. //convertion du fichier et redimensionnement du fichier
  43. int nbr_pixel_x = conf_taille_image / conf_taille_pixel;
  44. std::string commande("convert " + nom_fichier + " -resize " + std::to_string(nbr_pixel_x) + " " + nom_fichier + ".bmp");
  45. system(commande.c_str());
  46. nom_fichier_bmp = nom_fichier + ".bmp";
  47. if(entete() == 0)
  48. {
  49. if(donnees() == 0)
  50. {sortie();}
  51. else
  52. {std::cout << "une erreur s'est produite" << std::endl;}
  53. }
  54. else
  55. {std::cout << "une erreur s'est produite" << std::endl;}
  56. }
  57. }
  58. void BMP2Gcode::position_depart(std::string &position)
  59. {
  60. pos_depart = position;
  61. }
  62. int BMP2Gcode::entete()
  63. {
  64. //ouverture du fichier
  65. std::ifstream fichier(nom_fichier_bmp, std::ifstream::binary);
  66. //test de l'ouverture du fichier
  67. if(!fichier)
  68. {
  69. std::cerr << "impossible d'ouvrir le fichier" << std::endl;
  70. return 1;
  71. }
  72. /*lecture de l'en tête de 54 octets composé comme suit :
  73. -2 octets [0-1] pour la signature
  74. -4 octets [2-5] pour la taille du fichier en octets
  75. -4 octets [6-9] de champ réservé
  76. -4 octets [10-13] pour l'offset
  77. -4 octets [14-17] pour la taille de l'en-tête de l'image (28 octets pour windows)
  78. -4 octets [18-21] pour la largeur de l'image
  79. -4 octets [22-25] pour la hauteur de l'image
  80. -2 octets [26-27] pour le nombre de plans (valeur toujours à 1)
  81. -2 octets [28-29] pour la profondeur de l'encodage (nbr de bit pour la couleur)
  82. -4 octets [30-33] pour la métode de compression (0 non compressé, 1 RLE 8bits/pixel, 2 RLE 4bits/pixel, 3 bitfields)
  83. -4 octets [35-38] pour la taille de l'image
  84. -4 octets [39-42] pour la résolution horizontale
  85. -4 octets [43-46] pour la résolution verticale
  86. -4 octets [47-50] pour la palette de couleur
  87. -4 octets [50-53] pour le nombre de couleurs importantes.
  88. */
  89. char *entete = new char [54];
  90. fichier.read(entete, 54);
  91. if(entete[0] == 'B')
  92. {
  93. if(entete[1] == 'M')
  94. {
  95. std::cout << "Bitmap windows" << std::endl;
  96. }
  97. }
  98. /*Pour récupérer les informations on va utiliser l'astuce suivante :
  99. pour la valeur commençant à entete[indice] on va commencer par prendre l'adresse :
  100. &entete[indice]
  101. puis on va caster ça dans le type qui correspond bien, int * si c'est sur 4 octets,
  102. short * si c'est sur 2 octets.
  103. (int *)&entete[indice] correspond donc à un pointeur de type int *, il suffit alors
  104. de prendre sa valeur en faisant : *(int *)entete[indice]
  105. */
  106. BMP_offset = *(int*)&entete[10];
  107. BMP_largeur = *(int*)&entete[18];
  108. BMP_hauteur = *(int*)&entete[22];
  109. BMP_profondeur = *(short*)&entete[28];
  110. std::cout << "compression : " << *(int*)&entete[30] << std::endl;
  111. std::cout << "offset BMP : " << BMP_offset << std::endl;
  112. std::cout << "largeur BMP : " << BMP_largeur << std::endl;
  113. std::cout << "hauteur BMP : " << BMP_hauteur << std::endl;
  114. std::cout << "profondeur BMP : " << BMP_profondeur << std::endl;
  115. delete[] entete;
  116. fichier.close();
  117. return 0;
  118. }
  119. int BMP2Gcode::donnees()
  120. {
  121. //ouverture du fichier
  122. std::ifstream fichier(nom_fichier_bmp, std::ifstream::binary);
  123. //test de l'ouverture du fichier
  124. if(!fichier)
  125. {
  126. std::cerr << "impossible d'ouvrir le fichier" << std::endl;
  127. return 1;
  128. }
  129. if(BMP_offset == 0)
  130. {entete();}
  131. /* /!\ATTENTION/!\
  132. 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
  133. Source : https://fr.wikipedia.org/wiki/Windows_bitmap#Disposition_des_donn%C3%A9es_de_l'image
  134. Il est donc nécessaire pour lire correctement les données de prévoir une limite qui est un multiple de 4
  135. mais également de sauter ces éventuels informations
  136. */
  137. int reste = (BMP_largeur * BMP_profondeur/8) % 4, longueur_ligne = BMP_largeur * BMP_profondeur/8;
  138. if(reste != 0)
  139. {
  140. //Si la longueur d'une ligne n'est un multiple de 4
  141. //il suffit alors d'ajouter à la ligne 4 - reste
  142. longueur_ligne += (4 - reste);
  143. }
  144. //Maintenant qu'on connait la taille réelle d'une ligne on peut calculer la limite
  145. int limite = longueur_ligne * BMP_hauteur + BMP_offset;
  146. char *donnees = new char [ limite ];
  147. fichier.read(donnees, limite);
  148. int depart = BMP_offset;
  149. for(int i = 0 ; i < BMP_hauteur ; i++)
  150. {
  151. //on récupère une ligne
  152. for(int j = depart ; j < depart + BMP_largeur * BMP_profondeur/8; j += BMP_profondeur/8)
  153. {
  154. if(donnees[j] < 0)
  155. {tab_donnees.push_back(256 + (int)donnees[j]);}
  156. else
  157. {tab_donnees.push_back((int)donnees[j]);}
  158. }
  159. depart += longueur_ligne;
  160. }
  161. delete[] donnees;
  162. fichier.close();
  163. return 0;
  164. }
  165. int BMP2Gcode::sortie()
  166. {
  167. std::string nom_fichier_gcode;
  168. nom_fichier_gcode = nom_fichier_bmp + ".gcode";
  169. std::ofstream fichier_sortie;
  170. fichier_sortie.open (nom_fichier_gcode);
  171. if(!fichier_sortie)
  172. {
  173. std::cerr << "impossible d'ouvrir le fichier de sortie" << std::endl;
  174. return -1;
  175. }
  176. /****************************CONDITIONS INITIALES****************************/
  177. int sens = 1, nbr_passage = 1, ligne = 0, colonne = 0;
  178. std::cout << "***SORTIE***" << std::endl;
  179. std::cout << "BMP_largeur = " << BMP_largeur << " mm" << std::endl;
  180. std::cout << "largeur = " << BMP_largeur * conf_taille_pixel << " mm" << std::endl;
  181. std::cout << "hauteur = " << BMP_hauteur * conf_taille_pixel << " mm" << std::endl;
  182. std::cout << "taille pixel = " << conf_taille_pixel << " mm" << std::endl;
  183. //On passe en mode relatif
  184. fichier_sortie << "G91" << std::endl;
  185. //si une position de départ est fournie
  186. if(pos_depart == "centrer")
  187. {
  188. fichier_sortie << "G0 F" << conf_vitesse_max << " X-" << (double)(BMP_largeur * conf_taille_pixel/2.0) << " Y-" << (double)(BMP_hauteur * conf_taille_pixel/2.0) << std::endl;
  189. std::cout << "la position de départ sera le centre en X et Y de l'objet" << std::endl;
  190. }
  191. else if(pos_depart == "centrerX")
  192. {
  193. fichier_sortie << "G0 F" << conf_vitesse_max << " X-" << (double)(BMP_largeur * conf_taille_pixel/2.0) << std::endl;
  194. std::cout << "la position de départ sera le centre en X l'objet" << std::endl;
  195. }
  196. //on s'assure que le laser est éteint
  197. fichier_sortie << "M106 P1 S0" << std::endl;
  198. //on défini la vitesse
  199. fichier_sortie << "G1 F" << conf_vitesse << std::endl;
  200. //on parcourt toutes les lignes
  201. while(ligne < BMP_hauteur)
  202. {
  203. //pour graver une ligne on fera des aller-retour tant que :
  204. do
  205. {
  206. int indice = ligne * BMP_largeur + colonne;
  207. unsigned int puissance = 0, indice_min = 0, indice_max = 0, nbr = 1;
  208. puissance = conversion(255 - tab_donnees[indice], conf_puissance_min, conf_puissance_max);
  209. indice_min = ligne * BMP_largeur;
  210. indice_max = indice_min + BMP_largeur - 1;
  211. //on détermine le nombre de pixels successifs pour lesquels la puissance du laser sera la même
  212. nbr = consecutif(indice, indice_min, indice_max, puissance, sens);
  213. //pour accélérer la gravure, on va augmenter la vitesse au dessus des blancs
  214. //c'est à dire quand la puissances du laser == 0
  215. if(puissance == 0)
  216. {fichier_sortie << "G1 F" << conf_vitesse_max << std::endl;}
  217. fichier_sortie << "M106 P1 S" << puissance << std::endl;
  218. //on se déplace selon le sens
  219. fichier_sortie << "G1 X" << sens * conf_taille_pixel * nbr << std::endl;
  220. colonne += sens * nbr;
  221. //ne pas oublier de rétablir la vitesse après les blancs
  222. if(puissance == 0)
  223. {fichier_sortie << "G1 F" << conf_vitesse << std::endl;}
  224. }
  225. while(colonne < BMP_largeur && colonne >= 0);
  226. /*arrivé ici on vient de tracer une ligne et le paramètre colonne est hors limite, il faut donc le rectifier.
  227. Il est peut être également nécessaire de faire un retour cela va dépendre du rapport
  228. entre la taille du pixel et la taille du laser*/
  229. if(colonne < 0)
  230. {colonne = 0;}
  231. if(colonne >= BMP_largeur)
  232. {colonne = BMP_largeur - 1;}
  233. if(conf_taille_pixel > conf_taille_laser * nbr_passage)
  234. {
  235. /*si on a pas fait un nombre de passage suffisent pour égaler la taille du pixel
  236. alors on repart dans l'autre sens en prennant soin de décaler la colonne
  237. et on recommence*/
  238. nbr_passage ++;
  239. }
  240. else
  241. {
  242. /*si le nombre de passage permet d'égaler la taille du pixel
  243. alors on change de ligne et on recommence*/
  244. ligne ++;
  245. nbr_passage = 1;
  246. }
  247. /*Dans les 2 cas il est nécessaire de se déplacer sur l'axe Y
  248. et de changer de sens*/
  249. fichier_sortie << "G1 Y" << conf_taille_laser << std::endl;
  250. sens *= -1;
  251. }
  252. //il ne faut pas oublier d'éteindre le laser à la fini
  253. fichier_sortie << "M106 P1 S0" << std::endl;
  254. fichier_sortie.close();
  255. return 0;
  256. }
  257. unsigned int BMP2Gcode::conversion(unsigned int valeur, unsigned int min, unsigned int max)
  258. {
  259. /*pour une valeur de 0 la puissance du laser doit être égale à min
  260. *pour une valeur de 255 la puissance du laser doit être égal à max
  261. *pour trouver la valeur entre les deux on utilise une fonction affine
  262. y = ax + b
  263. pour x = 0 <=> y = b = min
  264. pour x = 255 <=> y = 255*a+min = max
  265. <=> a = (max - min)/255
  266. */
  267. return valeur * (max - min)/255 + min;
  268. }
  269. unsigned int BMP2Gcode::consecutif(unsigned int indice, unsigned int min, unsigned int max, unsigned int puissance, int sens)
  270. {
  271. /*Cette fonction va déterminer le nombre de pixel consécutif
  272. avec la même couleur que le premier pixel, dans un interval donné
  273. du fait de la calibration il se peut que des teintes de gris proches
  274. soient associées à la même puissance laser,
  275. il est donc nécessaire de comparer les puissance et non les données des pixel*/
  276. unsigned int nbr = 0;
  277. while(indice >= min && indice <= max)
  278. {
  279. if(puissance == conversion(255 - tab_donnees[indice], conf_puissance_min, conf_puissance_max))
  280. {nbr ++;}
  281. else
  282. {return nbr;}
  283. indice += sens;
  284. }
  285. return nbr;
  286. }
  287. int BMP2Gcode::fichier_configuration(std::string &conf)
  288. {
  289. int nbr_parametres = 0;
  290. //On regarde si le fichier de configuration existe déjà
  291. std::ifstream fichier_conf(conf);
  292. //s'il n'existe pas
  293. if(!fichier_conf)
  294. {
  295. //alors on le crée
  296. std::ofstream fichier_conf;
  297. if(conf.empty())
  298. {fichier_conf.open("laser.conf");}
  299. else
  300. {fichier_conf.open(conf);}
  301. fichier_conf << "taille_laser(mm) 0.2" << std::endl;
  302. fichier_conf << "puissance_min 0" << std::endl;
  303. fichier_conf << "puissance_max 255" << std::endl;
  304. fichier_conf << "vitesse(mm/min) 1300" << std::endl;
  305. fichier_conf << "vitesse_max(mm/min) 8000" << std::endl;
  306. fichier_conf << "taille_image_x(mm) 150" << std::endl;
  307. fichier_conf << "taille_pixel(mm) 0.4" << std::endl;
  308. std::cout << "*************************************************" <<std::endl;
  309. std::cout << "ATTENTION, un fichier de configuration a été crée" <<std::endl;
  310. std::cout << "Il ne contient probablement pas les bonnes valeurs" <<std::endl;
  311. std::cout << "Pour les connaitre il est nécessaire d'utiliser" <<std::endl;
  312. std::cout << "les scripts d'étalonnages" <<std::endl;
  313. std::cout << "*************************************************" <<std::endl;
  314. conf_taille_laser = 0.2;
  315. conf_puissance_min = 0;
  316. conf_puissance_max = 255;
  317. conf_vitesse = 1300;
  318. conf_vitesse_max = 8000;
  319. conf_taille_image = 150;
  320. conf_taille_pixel = 0.4;
  321. }
  322. else
  323. {
  324. //s'il existe on récupère les valeurs
  325. std::string clef;
  326. double valeur;
  327. while(fichier_conf >> clef >> valeur)
  328. {
  329. if(clef == "taille_laser(mm)")
  330. {conf_taille_laser = valeur;}
  331. if(clef == "puissance_min")
  332. {conf_puissance_min = valeur;}
  333. if(clef == "puissance_max")
  334. {conf_puissance_max = valeur;}
  335. if(clef == "vitesse(mm/min)")
  336. {conf_vitesse = valeur;}
  337. if(clef == "vitesse_max(mm/min)")
  338. {conf_vitesse_max = valeur;}
  339. if(clef == "taille_image_x(mm)")
  340. {conf_taille_image = valeur;}
  341. if(clef == "taille_pixel(mm)")
  342. {conf_taille_pixel = valeur;}
  343. }
  344. //vérification des paramètres
  345. if(conf_taille_laser == -1)
  346. {
  347. std::cout << "ATTENTION ! La taille du laser n'a pas été fourni" << std::endl;
  348. return 3;
  349. }
  350. if(conf_puissance_min == -1)
  351. {
  352. std::cout << "ATTENTION ! La puissance minimale du laser n'a pas été fourni" << std::endl;
  353. return 3;
  354. }
  355. if(conf_puissance_max == -1)
  356. {
  357. std::cout << "ATTENTION ! La puissance maximale du laser n'a pas été fourni" << std::endl;
  358. return 3;
  359. }
  360. if(conf_vitesse == -1)
  361. {
  362. std::cout << "ATTENTION ! La vitesse du laser n'a pas été fourni" << std::endl;
  363. return 3;
  364. }
  365. if(conf_vitesse_max == -1)
  366. {
  367. std::cout << "ATTENTION ! La vitesse maximale du laser n'a pas été fourni" << std::endl;
  368. return 3;
  369. }
  370. if(conf_taille_image == -1)
  371. {
  372. std::cout << "ATTENTION ! La taille de l'image n'a pas été fourni" << std::endl;
  373. return 3;
  374. }
  375. if(conf_taille_pixel == -1)
  376. {
  377. std::cout << "ATTENTION ! La taille du pixel n'a pas été fourni" << std::endl;
  378. return 3;
  379. }
  380. std::cout << "***CONFIGURATION***" << std::endl;
  381. std::cout << "taille_laser = " << conf_taille_laser << " mm" << std::endl;
  382. std::cout << "puissance_min = " << conf_puissance_min << " PWM 0-255" << std::endl;
  383. std::cout << "puissance_max = " << conf_puissance_max << " PWM 0-255" << std::endl;
  384. std::cout << "vitesse = " << conf_vitesse << " mm/min" << std::endl;
  385. std::cout << "vitesse_max = " << conf_vitesse_max << " mm/min" << std::endl;
  386. std::cout << "taille_image = " << conf_taille_image << " mm" << std::endl;
  387. std::cout << "taille_pixel = " << conf_taille_pixel << " mm" << std::endl;
  388. }
  389. fichier_conf.close();
  390. return 0;
  391. }
  392. void aide()
  393. {
  394. std::cout << "Ce programme permet de générer une fichier gcode à partir d'une image." << std::endl;
  395. std::cout << "Pour fonctionner il nécessite imagemagik afin de convertir et redimensionner les images" << std::endl;
  396. std::cout << std::endl;
  397. std::cout << "Les options :" << std::endl;
  398. std::cout << "-h\taffiche cette aide" << std::endl;
  399. std::cout << "-c\tpour préciser le fichier de configuration" << std::endl;
  400. std::cout << "-i\tpour préciser l'image à traiter" << std::endl;
  401. std::cout << "-p\tpour préciser la position de départ (centrer pour centrer en X et Y, centrerX pour uniquement centrer sur X)" << std::endl;
  402. }
  403. int main(int argc, char* argv[])
  404. {
  405. std::string conf, fichier, position;
  406. //Il est nécessaire d'avoir au moins 3 arguments
  407. if(argc >= 3)
  408. {
  409. //On cherche la position des arguments
  410. int position_c = -1, position_i = -1;
  411. for(int i = 0 ; i < (argc - 1) ; i++) //on ne va pas jusqu'au bout car chaque option nécessite un paramètre
  412. {
  413. //ici on récupère le nom du fichier de configuration placé après l'option -c
  414. if(std::string(argv[i]) == "-c")
  415. {conf = argv[i + 1];}
  416. if(std::string(argv[i]) == "-i")
  417. {fichier = argv[i + 1];}
  418. if(std::string(argv[i]) == "-p")
  419. {position = argv[i + 1];}
  420. }
  421. /*2 fichiers sont nécessaire,
  422. le fichier de configuration et le fichier image,
  423. on vérifie donc qu'ils aient été fourni*/
  424. if(fichier.empty())
  425. {
  426. std::cout << "ATTENTION ! Le fichier image n'a pas été fourni" << std::endl;
  427. std::cout << std::endl;
  428. aide();
  429. return 2;
  430. }
  431. BMP2Gcode image;
  432. image.position_depart(position);
  433. if(image.fichier_configuration(conf) != 0)
  434. {return 3;}
  435. image.traitement(fichier);
  436. }
  437. else
  438. {aide();}
  439. return 0;
  440. }