Verified Commit e81ee349 authored by Elouan Wauquier's avatar Elouan Wauquier
Browse files

Initial commit

parents
Pipeline #33436 passed with stage
in 36 seconds
# Any local working backup of a file
**/*.old
# Any build directory generated by Qt Creator
/build-*/
# The specific .pro.user file used by Qt Creator
**/*.pro.user*
# The build directory of the LaTeX report except .latexmkrc
/report/build/**
!/report/build/.latexmkrc
image: blang/latex:ctanfull
report:
script:
- apt-get update
- apt-get install -y python-pygments
- cd report/build
- latexmk -pdf ../tex/main.tex
- mv main.pdf ../../report.pdf
artifacts:
paths:
- "report.pdf"
expire_in: 1 weeks
tags:
- docker
#include "Analyzer.hpp"
Analyzer::Analyzer()
: m_sourceImage(nullptr),
m_transformedImage(new QImage),
m_rasterizedSection(new QImage),
m_ridgeDistance(0) {
// Ajoute tous les FIS
m_engines.insert(MinutiaEngine::Engine::Ending, new EndingEngine);
m_engines.insert(MinutiaEngine::Engine::Bifurcation, new BifurcationEngine);
m_minutiaColors.insert(MinutiaEngine::Engine::Ending, new QColor(255, 0, 0)); // Rouge
m_minutiaColors.insert(MinutiaEngine::Engine::Bifurcation, new QColor(0, 255, 0)); // Vert
for (int i(0); i < 256; ++i) { // Créé les nuances de gris
m_rasterizedSection->setColor(i, qRgb(i, i, i));
}
// Détermine `m_scanningDivision` de façon constante
m_scanningDivision.resize(STEP_PER_RIDGE * RIDGE_COUNT / 2);
for (int i(0); i < m_scanningDivision.size(); ++i) {
m_scanningDivision[i] = qCeil(2 * M_PI * (i + CENTER_RADIUS_IGNORE * STEP_PER_RIDGE));
}
}
void Analyzer::doLogPolarTransformation(const QImage& src, QPoint center, qreal angle) {
/* Converti l'image `src` en coordonnées polaires logarithmiques
*
* Cette méthode doit être appelée _après_ `doRidgeDistanceCalculation car elle a besoin
* de `m_ridgeDistance` pour ignorer le disque central
*
* Cette méthode doit être appelée _avant_ toute opération d'analyse de minuties car elle
* effectue une analyse préalable de l'image de l'empreinte digitale
*
* Crée une copie de `src` et `angle`, et une copie potentiellement corrigée de `center`
* comme attributs de l'objet
*
* - `src` est l'image de l'empreinte digitale en coordonnées cartésiennes
* - `center` est la position du centre de l'empreinte
* - `angle` est l'angle de départ de la transformation ($\theta_0$)
*/
if (m_ridgeDistance > 0) {
// Copie les données d'analyse
m_sourceImage = new QImage(src);
m_angle = angle;
// Assure que `center' est à l'intérieur de `m_sourceImage'
if (center.x() < 0) {
center.setX(0);
} else if (center.x() >= m_sourceImage->width()) {
center.setX(m_sourceImage->width() - 1);
}
if (center.y() < 0) {
center.setY(0);
} else if (center.y() >= m_sourceImage->height()) {
center.setY(m_sourceImage->height() - 1);
}
m_center = QPointF(static_cast<qreal>(center.x()), static_cast<qreal>(center.y()));
// Initialise
qreal greatestDistance(0); // Distance entre `center' et le côté le plus éloigné
int height; // Hauteur de l'image transformée (rayon, en pixels)
int width; // Largeur de l'image transformée (angle, en pixels)
greatestDistance = static_cast<qreal>(qMax(qMax(m_center.x(), static_cast<qreal>(m_sourceImage->width()) - 1 - m_center.x()),
qMax(m_center.y(), static_cast<qreal>(m_sourceImage->height()) - 1 - m_center.y())));
// Calcule la résolution de `m_transformedImage`
// `height` et `width` doivent être cohérents avec la résolution de `m_sourceImage'
qreal gamma = qAtan(1 / greatestDistance);
m_radius = 1 / gamma;
width = static_cast<int>(2 * M_PI / gamma);
height = static_cast<int>(m_radius * qLn(m_radius / (CENTER_RADIUS_IGNORE * m_ridgeDistance)));
// Construction de l'image transformée
if (m_transformedImage) {
delete m_transformedImage;
}
m_transformedImage = new QImage(width, height, QImage::Format_Indexed8);
for (int i = 0; i < 256; ++i) {
m_transformedImage->setColor(i, qRgb(i, i, i));
}
// Composition pixel par pixel
QPointF srcPoint;
for (int x = 0; x < width; ++x) { // Angle
for (int y = 0; y < height; ++y) { // Rayon
srcPoint = logPolarToCartesian(QPointF(x, y));
if (srcPoint.x() < 0 || srcPoint.x() >= m_sourceImage->width() || srcPoint.y() < 0 || srcPoint.y() >= m_sourceImage->height()) { // Point en dehors de `m_sourceImage`
m_transformedImage->setPixel(x, y, 255); // Blanc par défaut
} else {
m_transformedImage->setPixel(x, y, static_cast<uint>(m_sourceImage->pixelColor(static_cast<int>(srcPoint.x()), static_cast<int>(srcPoint.y())).lightness()));
}
}
}
#ifdef SAVE_TRANSFORMED_IMAGE
m_transformedImage->save("transformed-image.png");
#endif
emit imageTransformed(m_transformedImage);
}
}
void Analyzer::doRidgeDistanceCalculation(const QImage& src, QPoint center, qreal angle) {
/* Calcule la distance moyenne entre deux sillons
*
* Permet d'abstraire la résolution de l'image et de travailler uniquement en fonction de
* la taille d'un sillon
*
* Un sillon est l'ensemble "bande blanche + bande noire"
*
* `src` est l'image de l'empreinte digitale originale
* `center` est la position du centre de l'empreinte
* `angle` est l'angle de départ de la transformation ($\theta_0$)
*/
QByteArray line; // Liste des intensités lumineuses le long de la ligne de scan
QPointF pixel(static_cast<qreal>(center.x()), static_cast<qreal>(center.y())); // Position du pixel de scan
const QPointF increment(qCos(angle), qSin(angle)); // Taille de l'incrément de `pixel`
// Parcourt la ligne de scan
while (pixel.x() >= 0 && pixel.x() < src.width() && pixel.y() >= 0 && pixel.y() < src.height()) {
line.append(static_cast<char>(bilinearInterpolation(pixel, src)));
pixel += increment;
}
// Analyse de `line`
QList<int> dist; // Distribution des intensités
dist.reserve(256);
for (int i(0); i < 256; ++i) {
dist.append(0);
}
for (int i(0); i < line.size(); ++i) {
dist[static_cast<unsigned char>(line.at(i))] += 1;
}
// Détermine le seuil de luminosité du noir et du blanc à partir de `dist`
unsigned char blackThreshold(0);
unsigned char whiteThreshold(0);
for (int i(1); i < dist.size(); ++i) {
dist[i] += dist[i - 1]; // Intégration de la distribution
if (dist[i] < line.size() / 3) { // Au moins un tiers des pixels sont noirs
blackThreshold = static_cast<unsigned char>(i);
} else if (dist[i] < 2 * line.size() / 3) { // Au moins un tiers des pixels sont blancs
whiteThreshold = static_cast<unsigned char>(i);
}
}
++blackThreshold;
// Liste des positions des pics noirs
QList<int> blackPeaks;
bool isBlackPeak(false);
for (int i(0); i < line.size(); ++i) {
if (static_cast<unsigned char>(line.at(i)) > whiteThreshold && isBlackPeak) {
isBlackPeak = false;
} else if (static_cast<unsigned char>(line.at(i)) < blackThreshold && !isBlackPeak) {
isBlackPeak = true;
blackPeaks.append(i);
}
}
// Moyenne des distances entre les pics
m_ridgeDistance = (blackPeaks.back() - blackPeaks.front()) / (blackPeaks.size() - 1);
}
void Analyzer::doScanning() {
/* Analyse `m_transformedImage` en faisant appel aux FIS de `m_engines` */
if (m_transformedImage && m_ridgeDistance > 0) { // Vérifie l'état de l'`Analyzer`
QList<int> rasterSizes; // Liste des résolutions pour les FIS
rasterSizes.append(5);
int sectionCount; // Nombre de scan dans un anneau donné
QImage sectionImg;
for (QList<int>::iterator itSize(rasterSizes.begin()); itSize != rasterSizes.end(); ++itSize) {
// Génère une image de résolution `*itSize`
// Construit la section à la bonne résolution
if (m_rasterizedSection) { delete m_rasterizedSection; }
m_rasterizedSection = new QImage(*itSize, *itSize, QImage::Format_Indexed8);
for (int i(0); i < 256; ++i) { m_rasterizedSection->setColor(i, qRgb(i, i, i)); }
for (int ring(0); ring < m_scanningDivision.size(); ++ring) { // Anneau par anneau
sectionCount = m_scanningDivision[ring];
for (int section(0); section < sectionCount; ++section) { // Section par section d'anneau
sectionImg = getSectionImage(ring, section);
rasterize(sectionImg, m_rasterizedSection);
#ifdef SAVE_SCANNING_IMAGES
m_rasterizedSection->save("section-" + QString::number(*itSize) + "-" + QString::number(ring) + "_" + QString::number(section) + ".png");
#endif
// Analyse la section
for (QMap<MinutiaEngine::Engine, MinutiaEngine*>::iterator it(m_engines.begin()); it != m_engines.end(); ++it) {
if ((*it)->dataSize() == (*itSize)) {
(*it)->processImage(m_rasterizedSection);
qreal value = (*it)->value();
int engineIndex(static_cast<int>((*it)->engineType()));
// Initialise `m_results`
if (m_results[engineIndex].size() == ring) {
m_results[engineIndex].append(QVector<qreal>());
}
m_results[engineIndex][ring].append(value);
#ifdef SAVE_SCANNING_IMAGES_MINUTIAS
if (value > 0) {
m_rasterizedSection->save(QString::fromStdString((*it)->getName()) + "-" + QString::number(*itSize) + "-" + QString::number(ring) + "_" + QString::number(section) + ".png");
}
#endif
}
}
}
}
}
// Génère une image overlay pour chaque minutie
QImage rawImage;
QRect cropRect;
QPainter imageMerger;
for (int i(0); i < static_cast<int>(MinutiaEngine::Engine::NUMBER_OF_ENGINE); ++i) {
MinutiaEngine::Engine engine = static_cast<MinutiaEngine::Engine>(i);
if (m_minutiaImages.size() == i) {
m_minutiaImages.insert(engine, new QImage(m_sourceImage->width(), m_sourceImage->height(), QImage::Format_ARGB32_Premultiplied));
}
m_minutiaImages[engine]->fill(Qt::transparent);
rawImage = getMinutiaImage(engine);
cropRect.setTop(rawImage.height() / 2 - static_cast<int>(m_center.y()));
cropRect.setLeft(rawImage.width() / 2 - static_cast<int>(m_center.x()));
cropRect.setHeight(m_sourceImage->height());
cropRect.setWidth(m_sourceImage->width());
imageMerger.begin(m_minutiaImages[engine]);
imageMerger.drawImage(m_sourceImage->rect(), *m_sourceImage, m_sourceImage->rect());
imageMerger.drawImage(m_sourceImage->rect(), rawImage, cropRect);
imageMerger.end();
#ifdef SAVE_MINUTIA_IMAGES
m_minutiaImages[engine]->save(QString::fromStdString(m_engines[engine]->getName() + ".png"));
#endif
}
// Génère une image overlay avec toutes les minuties
if (m_minutiaImages.size() == static_cast<int>(MinutiaEngine::Engine::NUMBER_OF_ENGINE)) {
m_minutiaImages.insert(MinutiaEngine::Engine::NUMBER_OF_ENGINE,
new QImage(m_sourceImage->width(), m_sourceImage->height(),
QImage::Format_ARGB32_Premultiplied));
}
m_minutiaImages[MinutiaEngine::Engine::NUMBER_OF_ENGINE]->fill(Qt::transparent);
imageMerger.begin(m_minutiaImages[MinutiaEngine::Engine::NUMBER_OF_ENGINE]);
imageMerger.drawImage(m_sourceImage->rect(), *m_sourceImage, m_sourceImage->rect());
for (int i(0); i < static_cast<int>(MinutiaEngine::Engine::NUMBER_OF_ENGINE); ++i) {
MinutiaEngine::Engine engine = static_cast<MinutiaEngine::Engine>(i);
rawImage = getMinutiaImage(engine);
cropRect.setTop(rawImage.height() / 2 - static_cast<int>(m_center.y()));
cropRect.setLeft(rawImage.width() / 2 - static_cast<int>(m_center.x()));
cropRect.setHeight(m_sourceImage->height());
cropRect.setWidth(m_sourceImage->width());
imageMerger.drawImage(m_sourceImage->rect(), rawImage, cropRect);
}
imageMerger.end();
}
emit scanningDone(m_minutiaImages[MinutiaEngine::Engine::NUMBER_OF_ENGINE]);
}
QImage Analyzer::getMinutiaImage(MinutiaEngine::Engine engineType) const {
/* Retourne une image carrée à la résolution de `m_sourceImage` contenant une
* heightmap à la position de la minutie reconnue par `engineType`.
*/
// Initialise l'image
int imageSize = static_cast<int>(RIDGE_COUNT * m_ridgeDistance);
QImage minutiaImage(imageSize, imageSize, QImage::Format_ARGB32_Premultiplied);
minutiaImage.fill(QColor(255, 255, 255, 0)); // Blanc transparent
// Outils de dessin
QPainter painter(&minutiaImage);
painter.setBackground(QColor(255, 255, 255, 0));
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
painter.setRenderHints(QPainter::Antialiasing);
QPainterPath path;
//Positionnement
const QPointF imageCenter(static_cast<qreal>(imageSize) / 2, static_cast<qreal>(imageSize) / 2);
QPointF globalOffset(imageCenter - m_center);
const QVector<QVector<qreal>> *results(&(m_results[static_cast<int>(engineType)])); // Tableau 2D des degrés de présence de la minutie reconnue par `engineType`
QColor *color(m_minutiaColors[engineType]); // Couleur associée à la minutie reconnue par `engineType`
if (m_transformedImage && m_ridgeDistance > 0 && results->size() > 0) { // Vérifie l'état de l'`Analyzer`
int yLogPolarBegin(0); // Rayon (dans le système de coordonnées logarithmiques polaires) du début de l'anneau (inclu)
QPointF origin(logPolarToCartesian(QPointF(- m_angle * m_radius, yLogPolarBegin))); // Point (dans le système de coordonnées cartésiennes) du début de l'anneau
int sideSize(static_cast<int>(cartesianToLogPolar(origin + QPointF(PIXEL_SIZE * m_ridgeDistance * m_rasterizedSection->height(), 0)).y() - yLogPolarBegin)); // Côté d'une section
qreal littleOffset, bigOffset; // Offsets du début et de la fin (resp.) du pixel central de la section par rapport à `yLogPolarBegin`
int sectionCount; // Nombre de scan dans un anneau donné
for (int ring(0); ring < m_scanningDivision.size(); ++ring) { // Anneau par anneau
// Initialisation
littleOffset = static_cast<qreal>(sideSize) * (m_engines[engineType]->dataSize() / 2) / m_engines[engineType]->dataSize();
bigOffset = static_cast<qreal>(sideSize) * (m_engines[engineType]->dataSize() / 2 + 1) / m_engines[engineType]->dataSize();
sectionCount = m_scanningDivision[ring];
for (int section(0); section < sectionCount; ++section) { // Section par section d'anneau
// Construit une section de l'image
// Calcule l'aire couverte par le pixel central dans le système de coordonnées cartésiennes
QRectF polarRect; // Aire dans le système de coodonnées polaire
polarRect.setTop(yLogPolarBegin + littleOffset);
polarRect.setLeft(m_transformedImage->width() * static_cast<qreal>(section) / sectionCount - sideSize / 2 + littleOffset);
polarRect.setHeight(bigOffset - littleOffset);
polarRect.setWidth(bigOffset - littleOffset);
QPointF cartesianNW(logPolarToCartesian(polarRect.topRight()) + globalOffset),
cartesianNE(logPolarToCartesian(polarRect.topLeft()) + globalOffset),
cartesianSW(logPolarToCartesian(polarRect.bottomRight()) + globalOffset),
cartesianSE(logPolarToCartesian(polarRect.bottomLeft()) + globalOffset); // Coins dans le système de coordonnées cartésiennes
qreal bigR(qSqrt(qPow(imageCenter.x() - cartesianNW.x(), 2) +
qPow(imageCenter.y() - cartesianNW.y(), 2)));
qreal littleR(qSqrt(qPow(imageCenter.x() - cartesianSW.x(), 2) +
qPow(imageCenter.y() - cartesianSW.y(), 2)));
qreal startAngle(qRadiansToDegrees(qAtan2(imageCenter.y() - cartesianNW.y(),
cartesianNW.x() - imageCenter.x())));
qreal endAngle(qRadiansToDegrees(qAtan2(imageCenter.y() - cartesianNE.y(),
cartesianNE.x() - imageCenter.x())));
qreal sweepAngle(endAngle - startAngle);
if (sweepAngle < 0) {
sweepAngle += 360;
}
if (sweepAngle >= 180) {
sweepAngle = 360 - sweepAngle;
}
if (results->at(ring).at(section) > 0) {
// Dessine l'aire
path = QPainterPath();
path.moveTo(cartesianNW);
path.arcTo(imageCenter.x() - bigR, imageCenter.y() - bigR,
2 * bigR, 2 * bigR, startAngle, sweepAngle); // cartesianNE
path.lineTo(cartesianSE);
path.arcTo(imageCenter.x() - littleR, imageCenter.y() - littleR,
2 * littleR, 2 * littleR, endAngle, - sweepAngle); // cartesianSW
path.closeSubpath(); // cartesianNW
color->setAlphaF(results->at(ring).at(section));
painter.fillPath(path, *color);
}
}
// Met à jour les bornes de l'anneau
yLogPolarBegin = static_cast<int>(cartesianToLogPolar(m_center + QPointF((static_cast<qreal>(ring) / STEP_PER_RIDGE + CENTER_RADIUS_IGNORE) * m_ridgeDistance, 0)).y());
origin = logPolarToCartesian(QPointF(- m_angle * m_radius, yLogPolarBegin));
sideSize = static_cast<int>(cartesianToLogPolar(origin + QPointF(PIXEL_SIZE * m_ridgeDistance * m_engines[engineType]->dataSize(), 0)).y() - yLogPolarBegin);
}
painter.end();
}
return minutiaImage;
}
void Analyzer::pasteOnImage(QImage& src1, const QImage& src2, QPoint pos) {
/* Copie `src2` par-dessus `src1` à la position `pos`
*
* Cette méthode _modifie_ `src1`
*
* Ne fonctionne qu'en nuances de gris (`QImage::Format_Indexed8`) et ne gère pas la transparence
*/
if (src1.format() == QImage::Format_Indexed8) { // Vérifie le format de l'image
for (int i(0); i < 256; ++i) { // Créé les nuances de gris
src1.setColor(i, qRgb(i, i, i));
}
for (int x(pos.x()); x < src1.width() && x - pos.x() < src2.width(); ++x) { // Lignes (x)
for (int y(pos.y()); y < src1.height() && y - pos.y() < src2.height(); ++y) { // Colonnes (y)
int value = src2.pixelColor(x - pos.x(), y - pos.y()).lightness();
src1.setPixel(x, y, static_cast<uint>(value));
}
}
}
}
QPointF Analyzer::logPolarToCartesian(const QPointF& src) const {
/* Converti une coordonnée logarithmique polaire en coordonnée cartésienne
*
* Cette transformation prend en compte les modalités de transformation de `doLogPolarTranformation` :
* - `m_radius`
* - `m_angle`
* - `m_center`
*
* `src` est le point en coordonnées logarithmiques polaires
*/
QPointF dest;
dest.setX(m_radius * qExp((src.y() - m_transformedImage->height()) / m_radius) * qCos(src.x() / m_radius + m_angle) + m_center.x() + 0.5);
dest.setY(m_radius * qExp((src.y() - m_transformedImage->height()) / m_radius) * qSin(src.x() / m_radius + m_angle) + m_center.y() + 0.5);
return dest;
}
QPointF Analyzer::cartesianToLogPolar(const QPointF& src) const {
/* Converti une coordonnée cartésienne en coordonnée logarithmique polaire s
*
* Cette transformation prend en compte les modalités de transformation de `doLogPolarTranformation` :
* - `m_radius`
* - `m_angle`
* - `m_center`
*
* `src` est le point en coordonnées cartésiennes
*/
QPointF dest;
qreal x = (src.x() - m_center.x()) / m_radius;
qreal y = (src.y() - m_center.y()) / m_radius;
dest.setX(m_radius * (qAtan2(y, x) - m_angle));
dest.setY(m_transformedImage->height() + m_radius * qLn(qSqrt(qPow(x, 2) + qPow(y, 2))));
// `dest.x()` mod `m_transformedImage.width()`
// i.e. valeur angulaire modulo maximum de la valeur angulaire
while (dest.x() < 0) {
dest.setX(dest.x() + m_transformedImage->width());
}
while (dest.x() >= m_transformedImage->width()) {
dest.setX(dest.x() - m_transformedImage->width());
}
return dest;
}
QImage Analyzer::getSectionImage(int ring, int section) const {
/* Retourne la section de `m_transformedImage` aux coordonnées `yLogPolarBegin` et `angularRatio`
*
* `angularRatio` est compris entre 0 et 1
*/
int yLogPolarBegin = static_cast<int>(cartesianToLogPolar(m_center + QPointF((static_cast<qreal>(ring) / STEP_PER_RIDGE + CENTER_RADIUS_IGNORE) * m_ridgeDistance, 0)).y());
int sideSize;
QPointF origin = logPolarToCartesian(QPointF(- m_angle * m_radius, yLogPolarBegin));
sideSize = static_cast<int>(cartesianToLogPolar(origin + QPointF(PIXEL_SIZE * m_ridgeDistance * m_rasterizedSection->height(), 0)).y() - yLogPolarBegin);
qreal angularRatio = static_cast<qreal>(section) / m_scanningDivision[ring];
QRect sectionRect;
sectionRect.setTop(yLogPolarBegin);
sectionRect.setHeight(sideSize);
sectionRect.setLeft(static_cast<int>(m_transformedImage->width() * angularRatio - sideSize / 2));
sectionRect.setWidth(sideSize);
QImage sectionImg = m_transformedImage->copy(sectionRect);
// Reconstruit la partie manquante de l'image
if (sectionRect.left() < 0) {
pasteOnImage(sectionImg,
m_transformedImage->copy(m_transformedImage->width() + sectionRect.left(), yLogPolarBegin, - sectionRect.left(), sectionRect.height()),
QPoint(0, 0));
} else if (sectionRect.right() >= m_transformedImage->width()) {
pasteOnImage(sectionImg,
m_transformedImage->copy(0, yLogPolarBegin, sectionRect.right() - m_transformedImage->width() + 1, sectionRect.height()),
QPoint(sectionRect.width() - sectionRect.right() + m_transformedImage->width() - 1, 0));
}
return sectionImg;
}
void Analyzer::rasterize(const QImage& src, QImage *dest) const {
/* Réduit la résolution de `src` à celle de `dest` par interpolation bilinéaire
*
* `dest` doit être au format `QImage::Format_Indexed8` et déjà indexé en nuances de gris
*
* Voir : https://en.wikipedia.org/wiki/Bilinear_interpolation#/media/File:Bilinear_interpolation_visualisation.svg
*/
qreal ratio = static_cast<qreal>(src.width()) / static_cast<qreal>(dest->width()); // Taux de réduction
qreal value; // Luminosité d'un pixel
for (int x(0); x < dest->width(); ++x) {
for (int y(0); y < dest->height(); ++y) {
// Calcule `value` pour le pixel (x, y) de `m_rasterizedImage`
value = 0;
// Section de `src` correspondant à (x, y)
QRectF rectF(x * ratio, y * ratio, ratio, ratio);
if (rectF.right() > src.width()) {
rectF.setRight(src.width());
}
if (rectF.bottom() > src.height()) {
rectF.setBottom(src.height());
}
// Intérieur strict de la section `rectF` (distance à la bordure comprise dans ]0,1])
QRect rect(qFloor(rectF.left()) + 1,
qFloor(rectF.top()) + 1,
qCeil(rectF.right()) - qFloor(rectF.left()) - 2,
qCeil(rectF.bottom()) - qFloor(rectF.top()) - 2);
qreal leftOffset(rect.left() - rectF.left());
qreal topOffset(rect.top() - rectF.top());
qreal rightOffset(rectF.right() - rect.right() - 1);
qreal bottomOffset(rectF.bottom() - rect.bottom() - 1);
// Calcul des contributions des pixels de `rectF`
// Intérieur
for (int srcX(rect.left()); srcX <= rect.right(); ++srcX) {
for (int srcY(rect.top()); srcY <= rect.bottom(); ++srcY) {
value += src.pixelColor(srcX, srcY).lightnessF();
}
}
// Côtés
for (int srcY(rect.top()); srcY <= rect.bottom(); ++srcY) { // left
value += src.pixelColor(rect.left() - 1, srcY).lightnessF() * leftOffset;
}
for (int srcY(rect.top()); srcY <= rect.bottom(); ++srcY) { // right
value += src.pixelColor(rect.right() + 1, srcY).lightnessF() * rightOffset;
}
for (int srcX(rect.left()); srcX <= rect.right(); ++srcX) { // top
value += src.pixelColor(srcX, rect.top() - 1).lightnessF() * topOffset;
}
for (int srcX(rect.left()); srcX <= rect.right(); ++srcX) { // bottom
value += src.pixelColor(srcX, rect.bottom() + 1).lightnessF() * bottomOffset;
}
// Coins
value += src.pixelColor(rect.left() - 1, rect.top() - 1).lightnessF() * leftOffset * topOffset; // top-left
value += src.pixelColor(rect.right() + 1, rect.top() - 1).lightnessF() * rightOffset * topOffset; // top-right
value += src.pixelColor(rect.right() + 1, rect.bottom() + 1).lightnessF() * rightOffset * bottomOffset; // bottom-right
value += src.pixelColor(rect.left() - 1, rect.bottom() + 1).lightnessF() * leftOffset * bottomOffset; // bottom-left
// Moyenne
value = qRound(value * 255 / qPow(ratio, 2));
dest->setPixel(x, y, static_cast<uint>(value));
}
}
}
void Analyzer::getAnalysisCoordinates(qreal x, qreal y, int *ring, int *section) const {
QPointF logCoords(cartesianToLogPolar(QPointF(x, y)));
qreal yLogPolarBegin;
// Détermine l'anneau `ring`
*ring = -1;
for (int i(0); i < m_scanningDivision.size(); ++i) {
yLogPolarBegin = static_cast<int>(cartesianToLogPolar(m_center + QPointF((static_cast<qreal>(i) / STEP_PER_RIDGE + CENTER_RADIUS_IGNORE) * m_ridgeDistance, 0)).y());
if (yLogPolarBegin > logCoords.y()) {
*ring = i - 1 - ANALYZED_IMAGE_DEFAULT_RES / 2;
break;
}
}
if (*ring < 0) {
*ring = -1;
*section = -1;
return;
}
int sectionCount = m_scanningDivision[*ring];
qreal angleRatio = logCoords.x() / m_transformedImage->width();
*section = qRound(angleRatio * sectionCount);
}
MinutiaEngine::Engine Analyzer::getEngine(const QString &engineName) const {
/* Retourne l'id du FIS dont le nom est `engineName`
*
* Si le nom n'est pas connu, retourne MinutiaEngine::Engine::NUMBER_OF_ENGINE
*/
MinutiaEngine::Engine engine;
for (int i(0); i < m_engines.size(); ++i) {