205 lines
5.7 KiB
PHP
205 lines
5.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Domain\QrCode\Service;
|
|
|
|
use App\Domain\QrCode\ValueObject\MaskPattern;
|
|
|
|
class QrCodeMasker
|
|
{
|
|
/**
|
|
* Findet die beste Maske für die Matrix
|
|
*/
|
|
public function findBestMask(array $matrix, int $size): MaskPattern
|
|
{
|
|
$lowestPenalty = PHP_INT_MAX;
|
|
$bestMask = MaskPattern::PATTERN_0;
|
|
|
|
foreach (MaskPattern::cases() as $mask) {
|
|
$testMatrix = $matrix;
|
|
$this->applyMask($testMatrix, $size, $mask);
|
|
$penalty = $this->calculateMaskPenalty($testMatrix, $size);
|
|
|
|
if ($penalty < $lowestPenalty) {
|
|
$lowestPenalty = $penalty;
|
|
$bestMask = $mask;
|
|
}
|
|
}
|
|
|
|
return $bestMask;
|
|
}
|
|
|
|
/**
|
|
* Wendet eine Maske auf die Matrix an
|
|
*/
|
|
public function applyMask(array &$matrix, int $size, MaskPattern $maskPattern): void
|
|
{
|
|
for ($row = 0; $row < $size; $row++) {
|
|
for ($col = 0; $col < $size; $col++) {
|
|
if ($matrix[$row][$col] === null) {
|
|
continue;
|
|
}
|
|
|
|
if (!$this->isFunctionModule($row, $col, $size)) {
|
|
if ($maskPattern->shouldMask($row, $col)) {
|
|
$matrix[$row][$col] = !$matrix[$row][$col];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Berechnet die Strafpunkte für eine maskierte Matrix
|
|
*/
|
|
public function calculateMaskPenalty(array $matrix, int $size): int
|
|
{
|
|
$penalty = 0;
|
|
|
|
// Regel 1: Aufeinanderfolgende Module gleicher Farbe
|
|
$penalty += $this->evaluateConsecutiveModulesPenalty($matrix, $size);
|
|
|
|
// Regel 2: Blöcke gleicher Farbe
|
|
$penalty += $this->evaluateSameColorBlocksPenalty($matrix, $size);
|
|
|
|
// Regel 3: Muster, die Finder-Pattern ähneln
|
|
$penalty += $this->evaluateFinderPatternLikePenalty($matrix, $size);
|
|
|
|
// Regel 4: Ausgewogenheit schwarzer und weißer Module
|
|
$penalty += $this->evaluateBalancePenalty($matrix, $size);
|
|
|
|
return $penalty;
|
|
}
|
|
|
|
/**
|
|
* Evaluiert die Strafe für aufeinanderfolgende Module gleicher Farbe
|
|
*/
|
|
private function evaluateConsecutiveModulesPenalty(array $matrix, int $size): int
|
|
{
|
|
$penalty = 0;
|
|
|
|
// Zeilen prüfen
|
|
for ($row = 0; $row < $size; $row++) {
|
|
$count = 1;
|
|
for ($col = 1; $col < $size; $col++) {
|
|
if ($matrix[$row][$col] === $matrix[$row][$col - 1]) {
|
|
$count++;
|
|
} else {
|
|
if ($count >= 5) {
|
|
$penalty += 3 + ($count - 5);
|
|
}
|
|
$count = 1;
|
|
}
|
|
}
|
|
if ($count >= 5) {
|
|
$penalty += 3 + ($count - 5);
|
|
}
|
|
}
|
|
|
|
// Spalten prüfen
|
|
for ($col = 0; $col < $size; $col++) {
|
|
$count = 1;
|
|
for ($row = 1; $row < $size; $row++) {
|
|
if ($matrix[$row][$col] === $matrix[$row - 1][$col]) {
|
|
$count++;
|
|
} else {
|
|
if ($count >= 5) {
|
|
$penalty += 3 + ($count - 5);
|
|
}
|
|
$count = 1;
|
|
}
|
|
}
|
|
if ($count >= 5) {
|
|
$penalty += 3 + ($count - 5);
|
|
}
|
|
}
|
|
|
|
return $penalty;
|
|
}
|
|
|
|
/**
|
|
* Evaluiert die Strafe für 2x2 Blöcke gleicher Farbe
|
|
*/
|
|
private function evaluateSameColorBlocksPenalty(array $matrix, int $size): int
|
|
{
|
|
$penalty = 0;
|
|
|
|
for ($row = 0; $row < $size - 1; $row++) {
|
|
for ($col = 0; $col < $size - 1; $col++) {
|
|
$color = $matrix[$row][$col];
|
|
if ($color === $matrix[$row][$col + 1] &&
|
|
$color === $matrix[$row + 1][$col] &&
|
|
$color === $matrix[$row + 1][$col + 1]) {
|
|
$penalty += 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $penalty;
|
|
}
|
|
|
|
/**
|
|
* Evaluiert die Strafe für Muster, die wie Finder-Pattern aussehen
|
|
*/
|
|
private function evaluateFinderPatternLikePenalty(array $matrix, int $size): int
|
|
{
|
|
$penalty = 0;
|
|
// Vereinfachte Implementation
|
|
return $penalty;
|
|
}
|
|
|
|
/**
|
|
* Evaluiert die Strafe für unausgewogene schwarze/weiße Module
|
|
*/
|
|
private function evaluateBalancePenalty(array $matrix, int $size): int
|
|
{
|
|
$darkCount = 0;
|
|
$totalCount = $size * $size;
|
|
|
|
for ($row = 0; $row < $size; $row++) {
|
|
for ($col = 0; $col < $size; $col++) {
|
|
if ($matrix[$row][$col] === true) {
|
|
$darkCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
$darkPercentage = $darkCount * 100 / $totalCount;
|
|
$deviation = abs($darkPercentage - 50) / 5;
|
|
|
|
return (int) ($deviation * 10);
|
|
}
|
|
|
|
/**
|
|
* Prüft, ob ein Modul ein Funktionsmodul ist (also nicht maskiert werden sollte)
|
|
*/
|
|
private function isFunctionModule(int $row, int $col, int $size): bool
|
|
{
|
|
// Finder-Pattern und Separatoren
|
|
if (($row < 9 && $col < 9) ||
|
|
($row < 9 && $col >= $size - 8) ||
|
|
($row >= $size - 8 && $col < 9)) {
|
|
return true;
|
|
}
|
|
|
|
// Timing-Pattern
|
|
if ($row === 6 || $col === 6) {
|
|
return true;
|
|
}
|
|
|
|
// Format-Informationen
|
|
if (($row < 9 && $col === 8) || ($row === 8 && $col < 9) ||
|
|
($row === 8 && $col >= $size - 8) || ($row >= $size - 8 && $col === 8)) {
|
|
return true;
|
|
}
|
|
|
|
// Dark Module bei Version 1
|
|
if ($row === 8 && $col === 13) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|