captcha.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. /*
  2. * Copyright (c) 2014 Omkar Kanase
  3. *
  4. * This software is provided 'as-is', without any express or implied
  5. * warranty. In no event will the authors be held liable for any damages
  6. * arising from the use of this software.
  7. * Permission is granted to anyone to use this software for any purpose,
  8. * including commercial applications, and to alter it and redistribute it
  9. * freely, subject to the following restrictions:
  10. * 1. The origin of this software must not be misrepresented; you must not
  11. * claim that you wrote the original software. If you use this software
  12. * in a product, an acknowledgment in the product documentation would be
  13. * appreciated but is not required.
  14. * 2. Altered source versions must be plainly marked as such, and must not be
  15. * misrepresented as being the original software.
  16. * 3. This notice may not be removed or altered from any source distribution.
  17. *
  18. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  19. *
  20. * Copyright (c) 2022 Acetone
  21. * Updated for IRCaBot project
  22. * Usage example:
  23. * #include <QApplication>
  24. * int main(int argc, char *argv[]) {
  25. * QApplication a(argc, argv);
  26. * Captcha cp;
  27. * cp.randomize();
  28. * cp.generateText(5);
  29. * QFile f("f.png");
  30. * if (f.open(QIODevice::WriteOnly)) {
  31. * f.write(cp.captchaPngByteArray()); // ready to use PNG file
  32. * f.close();
  33. * }
  34. * qInfo() << cp.captchaText(); // answer
  35. * }
  36. */
  37. #include "captcha.h"
  38. Captcha::Captcha(QObject *parent) :
  39. QObject(parent)
  40. {
  41. m_hmod1 = 0.0;
  42. m_hmod2 = 0.0;
  43. m_vmod1 = 0.0;
  44. m_vmod2 = 0.0;
  45. m_font.setStyleStrategy(QFont::ForceOutline);
  46. m_font.setPointSize(40);
  47. m_font.setBold(true);
  48. m_font.setLetterSpacing(QFont::PercentageSpacing, QFont::SemiCondensed);
  49. m_captchaImage = QImage(200, 100, QImage::Format_RGB32);
  50. m_deformationType = Deform_SinCurve;
  51. m_captchaText = "NOTSET";
  52. m_textGeneration = TextGeneration_Random;
  53. m_fontColor = Qt::black;
  54. m_backColor = Qt::white;
  55. m_padding = 5;
  56. setDifficulty(3);
  57. }
  58. QByteArray Captcha::captchaPngByteArray() const
  59. {
  60. QByteArray data;
  61. QBuffer buff(&data);
  62. m_captchaImage.save(&buff, "PNG");
  63. return data;
  64. }
  65. void Captcha::setDifficulty(int val)
  66. {
  67. if (val == 0)
  68. {
  69. m_drawLines = false;
  70. m_drawEllipses = false;
  71. m_drawNoise = false;
  72. setSinDeform(10, 10, 5, 20);
  73. }
  74. else if (val == 1)
  75. {
  76. m_drawLines = true;
  77. m_lineWidth = 3;
  78. m_lineCount = 5;
  79. m_drawEllipses = false;
  80. m_drawNoise = false;
  81. setSinDeform(10, 15, 5, 20);
  82. }
  83. else if (val == 2)
  84. {
  85. m_drawLines = true;
  86. m_lineWidth = 2;
  87. m_lineCount = 5;
  88. m_drawEllipses = true;
  89. m_ellipseCount = 1;
  90. m_ellipseMinRadius = 20;
  91. m_ellipseMaxRadius = 40;
  92. m_drawNoise = false;
  93. setSinDeform(10, 15, 5, 15);
  94. }
  95. else if (val == 3)
  96. {
  97. m_drawLines = true;
  98. m_lineWidth = 2;
  99. m_lineCount = 3;
  100. m_drawEllipses = true;
  101. m_ellipseCount = 1;
  102. m_ellipseMinRadius = 20;
  103. m_ellipseMaxRadius = 40;
  104. m_drawNoise = true;
  105. m_noiseCount = 100;
  106. m_noisePointSize = 3;
  107. setSinDeform(8, 13, 5, 15);
  108. }
  109. else if (val == 4)
  110. {
  111. m_drawLines = true;
  112. m_lineWidth = 3;
  113. m_lineCount = 5;
  114. m_drawEllipses = true;
  115. m_ellipseCount = 1;
  116. m_ellipseMinRadius = 20;
  117. m_ellipseMaxRadius = 40;
  118. m_drawNoise = true;
  119. m_noiseCount = 100;
  120. m_noisePointSize = 3;
  121. setSinDeform(8, 10, 5, 10);
  122. }
  123. else
  124. {
  125. m_drawLines = true;
  126. m_lineWidth = 4;
  127. m_lineCount = 7;
  128. m_drawEllipses = true;
  129. m_ellipseCount = 1;
  130. m_ellipseMinRadius = 20;
  131. m_ellipseMaxRadius = 40;
  132. m_drawNoise = true;
  133. m_noiseCount = 200;
  134. m_noisePointSize = 3;
  135. setSinDeform(8, 10, 5, 10);
  136. }
  137. }
  138. QFont Captcha::font() const
  139. {
  140. return m_font;
  141. }
  142. QImage Captcha::captchaImage() const
  143. {
  144. return m_captchaImage;
  145. }
  146. Captcha::DeformType Captcha::deformationType() const
  147. {
  148. return m_deformationType;
  149. }
  150. QString Captcha::captchaText() const
  151. {
  152. return m_captchaText;
  153. }
  154. Captcha::TextGenerationMode Captcha::textGeneration() const
  155. {
  156. return m_textGeneration;
  157. }
  158. const QStringList &Captcha::dictionary() const
  159. {
  160. return m_dictionary;
  161. }
  162. QColor Captcha::fontColor() const
  163. {
  164. return m_fontColor;
  165. }
  166. QColor Captcha::backColor() const
  167. {
  168. return m_backColor;
  169. }
  170. bool Captcha::drawLines() const
  171. {
  172. return m_drawLines;
  173. }
  174. bool Captcha::drawEllipses() const
  175. {
  176. return m_drawEllipses;
  177. }
  178. bool Captcha::drawNoise() const
  179. {
  180. return m_drawNoise;
  181. }
  182. int Captcha::noiseCount() const
  183. {
  184. return m_noiseCount;
  185. }
  186. int Captcha::lineCount() const
  187. {
  188. return m_lineCount;
  189. }
  190. int Captcha::ellipseCount() const
  191. {
  192. return m_ellipseCount;
  193. }
  194. int Captcha::lineWidth() const
  195. {
  196. return m_lineWidth;
  197. }
  198. int Captcha::ellipseMinRadius() const
  199. {
  200. return m_ellipseMinRadius;
  201. }
  202. int Captcha::ellipseMaxRadius() const
  203. {
  204. return m_ellipseMaxRadius;
  205. }
  206. int Captcha::noisePointSize() const
  207. {
  208. return m_noisePointSize;
  209. }
  210. void Captcha::setFont(const QFont &arg)
  211. {
  212. m_font = arg;
  213. }
  214. void Captcha::setDeformationType(Captcha::DeformType arg)
  215. {
  216. m_deformationType = arg;
  217. }
  218. void Captcha::updateCaptcha()
  219. {
  220. QPainterPath path;
  221. QFontMetrics fm(m_font);
  222. if (m_deformationType == Deform_SinCurve)
  223. {
  224. path.addText(m_vmod2 + m_padding, m_hmod2 - m_padding + fm.height(), font(), captchaText());
  225. qreal sinrandomness = (static_cast<qreal>(qrand()) / RAND_MAX) * 5.0;
  226. for (int i = 0; i < path.elementCount(); ++i)
  227. {
  228. const QPainterPath::Element& el = path.elementAt(i);
  229. qreal y = el.y + sin(el.x / m_hmod1 + sinrandomness) * m_hmod2;
  230. qreal x = el.x + sin(el.y / m_vmod1 + sinrandomness) * m_vmod2;
  231. path.setElementPositionAt(i, x, y);
  232. }
  233. m_captchaImage = QImage(static_cast<int>(fm.horizontalAdvance(m_captchaText) + m_vmod2 * 2 + m_padding * 2),
  234. static_cast<int>(fm.height() + m_hmod2 * 2 + m_padding * 2), QImage::Format_RGB32);
  235. }
  236. m_captchaImage.fill(backColor());
  237. QPainter painter;
  238. painter.begin(&m_captchaImage);
  239. painter.setPen(Qt::NoPen);
  240. painter.setBrush(fontColor());
  241. painter.setRenderHint(QPainter::Antialiasing);
  242. painter.drawPath(path);
  243. if (m_drawLines)
  244. {
  245. painter.setPen(QPen(Qt::black, m_lineWidth));
  246. for (int i = 0; i < m_lineCount; i++)
  247. {
  248. int x1 = static_cast<int>(static_cast<qreal>(qrand()) / RAND_MAX) * m_captchaImage.width();
  249. int y1 = static_cast<int>(static_cast<qreal>(qrand()) / RAND_MAX) * m_captchaImage.height();
  250. int x2 = static_cast<int>(static_cast<qreal>(qrand()) / RAND_MAX) * m_captchaImage.width();
  251. int y2 = static_cast<int>(static_cast<qreal>(qrand()) / RAND_MAX) * m_captchaImage.height();
  252. painter.drawLine(x1, y1, x2, y2);
  253. }
  254. painter.setPen(Qt::NoPen);
  255. }
  256. if (m_drawEllipses)
  257. {
  258. for (int i = 0; i < m_ellipseCount; i++)
  259. {
  260. int x1 = static_cast<int>(m_ellipseMaxRadius / 2.0 + (static_cast<qreal>(qrand()) / RAND_MAX) * (m_captchaImage.width() - m_ellipseMaxRadius));
  261. int y1 = static_cast<int>(m_ellipseMaxRadius / 2.0 + (static_cast<qreal>(qrand()) / RAND_MAX) * (m_captchaImage.height() - m_ellipseMaxRadius));
  262. int rad1 = static_cast<int>(m_ellipseMinRadius + (static_cast<qreal>(qrand()) / RAND_MAX) * (m_ellipseMaxRadius - m_ellipseMinRadius));
  263. int rad2 = static_cast<int>(m_ellipseMinRadius + (static_cast<qreal>(qrand()) / RAND_MAX) * (m_ellipseMaxRadius - m_ellipseMinRadius));
  264. painter.setBrush(backColor());
  265. painter.setCompositionMode(QPainter::CompositionMode_Difference);
  266. painter.drawEllipse(QPoint(x1, y1), rad1, rad2);
  267. }
  268. }
  269. if (m_drawNoise)
  270. {
  271. for (int i = 0; i < m_noiseCount; i++)
  272. {
  273. int x1 = static_cast<int>(static_cast<qreal>(qrand()) / RAND_MAX) * m_captchaImage.width();
  274. int y1 = static_cast<int>(static_cast<qreal>(qrand()) / RAND_MAX) * m_captchaImage.height();
  275. QColor col = QColor(static_cast<int>(static_cast<qreal>(qrand()) / RAND_MAX) * 255, static_cast<int>(static_cast<qreal>(qrand()) / RAND_MAX) * 255, static_cast<int>(static_cast<qreal>(qrand()) / RAND_MAX) * 255);
  276. painter.setPen(QPen(col, m_noisePointSize));
  277. painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
  278. painter.drawPoint(x1, y1);
  279. }
  280. }
  281. painter.end();
  282. emit captchaGenerated(m_captchaImage, m_captchaText);
  283. }
  284. void Captcha::randomize()
  285. {
  286. qsrand(static_cast<uint>(QTime::currentTime().msec()));
  287. }
  288. void Captcha::setCaptchaText(QString arg)
  289. {
  290. m_captchaText = arg;
  291. }
  292. void Captcha::setTextGeneration(Captcha::TextGenerationMode arg)
  293. {
  294. if (m_textGeneration != arg) generateText(m_captchaText.size());
  295. m_textGeneration = arg;
  296. }
  297. void Captcha::setDictionary(const QStringList &arg)
  298. {
  299. m_dictionary = arg;
  300. }
  301. void Captcha::loadDictionary(QString FileName)
  302. {
  303. QFile file(FileName);
  304. if (!file.open(QIODevice::ReadOnly))
  305. {
  306. qCritical() << "Unable to open dictionary file";
  307. return;
  308. }
  309. m_dictionary.clear();
  310. QTextStream text(&file);
  311. QString str = text.readLine();
  312. while (str.size() > 0)
  313. {
  314. m_dictionary.append(str);
  315. str = text.readLine();
  316. }
  317. if (m_dictionary.size() <= 0)
  318. {
  319. qWarning() << "No data loaded from dictionary file";
  320. }
  321. }
  322. void Captcha::setFontColor(QColor arg)
  323. {
  324. m_fontColor = arg;
  325. }
  326. void Captcha::setBackColor(QColor arg)
  327. {
  328. m_backColor = arg;
  329. }
  330. void Captcha::setSinDeform(qreal hAmplitude, qreal hFrequency, qreal vAmplitude, qreal vFrequency)
  331. {
  332. m_deformationType = Deform_SinCurve;
  333. m_hmod1 = hFrequency;
  334. m_hmod2 = hAmplitude;
  335. m_vmod1 = vFrequency;
  336. m_vmod2 = vAmplitude;
  337. }
  338. QPair<QString, QImage> Captcha::generateCaptcha()
  339. {
  340. generateText(m_captchaText.size());
  341. return QPair<QString, QImage>(m_captchaText, m_captchaImage);
  342. }
  343. void Captcha::setDrawLines(bool arg)
  344. {
  345. m_drawLines = arg;
  346. }
  347. void Captcha::setDrawEllipses(bool arg)
  348. {
  349. m_drawEllipses = arg;
  350. }
  351. void Captcha::setDrawNoise(bool arg)
  352. {
  353. m_drawNoise = arg;
  354. }
  355. void Captcha::setNoiseCount(int arg)
  356. {
  357. m_noiseCount = arg;
  358. }
  359. void Captcha::setLineCount(int arg)
  360. {
  361. m_lineCount = arg;
  362. }
  363. void Captcha::setEllipseCount(int arg)
  364. {
  365. m_ellipseCount = arg;
  366. }
  367. void Captcha::setLineWidth(int arg)
  368. {
  369. m_lineWidth = arg;
  370. }
  371. void Captcha::setEllipseMinRadius(int arg)
  372. {
  373. m_ellipseMinRadius = arg;
  374. }
  375. void Captcha::setEllipseMaxRadius(int arg)
  376. {
  377. m_ellipseMaxRadius = arg;
  378. }
  379. void Captcha::setNoisePointSize(int arg)
  380. {
  381. m_noisePointSize = arg;
  382. }
  383. void Captcha::generateText(int noOfChars, bool includeNumbers, bool includeSymbols, bool allCapital)
  384. {
  385. if (noOfChars <= 0)
  386. {
  387. qWarning() << "Unable to generate text : Invalid number of characters";
  388. return;
  389. }
  390. QString text;
  391. if (m_textGeneration == TextGeneration_Random)
  392. {
  393. QVector<unsigned char> chars;
  394. for (int i = 0; i < noOfChars * 2; i++)
  395. {
  396. chars.push_back(65 + static_cast<unsigned char>(static_cast<qreal>(qrand()) / RAND_MAX) * (90 - 65));
  397. if (!allCapital) chars.push_back(97 + static_cast<unsigned char>(static_cast<qreal>(qrand()) / RAND_MAX) * (122 - 97));
  398. if (includeNumbers) chars.push_back(48 + static_cast<unsigned char>(static_cast<qreal>(qrand()) / RAND_MAX) * (57 - 48));
  399. if (includeSymbols) chars.push_back(33 + static_cast<unsigned char>(static_cast<qreal>(qrand()) / RAND_MAX) * (47 - 33));
  400. }
  401. for (int i = 0; i < noOfChars; i++)
  402. {
  403. text += static_cast<char>(chars[static_cast<int>((qrand() / static_cast<qreal>(RAND_MAX)) * (chars.size() - 1.0))]);
  404. }
  405. m_captchaText = text;
  406. }
  407. else if (m_textGeneration == TextGeneration_Dictionary)
  408. {
  409. if (m_dictionary.size() <= 5)
  410. {
  411. qWarning() << "In text generation : Dictionary size is too small";
  412. return;
  413. }
  414. m_captchaText = m_dictionary[static_cast<int>((qrand() / static_cast<qreal>(RAND_MAX)) * (m_dictionary.size() - 1.0))];
  415. }
  416. else
  417. {
  418. qWarning() << "Unable to generate text : Invalid text generation mode";
  419. }
  420. updateCaptcha();
  421. }