Add captcha image generation functionality.

This commit is contained in:
2023-06-28 17:29:56 +06:00
parent 81635b4efa
commit a35b8db281
26 changed files with 1211 additions and 5 deletions

117
app/Captcha/Images/Body.php Normal file
View File

@@ -0,0 +1,117 @@
<?php declare(strict_types=1);
namespace App\Captcha\Images;
use App\Captcha\Config\ImageBody as ImageBodyConfig;
use App\Captcha\Contracts\ImageLines;
use App\Captcha\Dto\Coordinators;
use App\Captcha\Dto\Image as ImageDto;
use App\Captcha\Dto\ImageBody as ImageBodyDto;
use App\Captcha\Dto\Sector;
use App\Captcha\Dto\Sectors;
use App\Captcha\Dto\Symbols;
use App\Captcha\Contracts\ImageBody;
use App\Captcha\Enums\SymbolType;
use App\Captcha\Contracts\ImageManager;
final class Body implements ImageBody
{
public function __construct(
private readonly ImageManager $imageManager,
private readonly ImageLines $imageLines
) { }
public function processing(Symbols $symbols, ImageBodyConfig $config): ImageBodyDto
{
$image = $this->imageManager->createImage($config->getWidth(), $config->getHeight());
$image->insertBackground($config->randomBackground());
$imageData = match ($symbols->getType()) {
SymbolType::String => $this->processingString($image, $symbols, $config),
};
$image = $imageData['image'];
if ($config->getNumberLines() > 0) {
$this->imageLines->processing(image: $image, colors: $config->getLineColors(), lines: $config->getNumberLines());
}
$image = new ImageDto(
imageBase64: $image->encode(),
width: $image->getWidth(),
height: $image->getHeight()
);
return new ImageBodyDto(
image: $image,
coordinators: $imageData['coordinators']
);
}
private function processingString(Image $image, Symbols $symbols, ImageBodyConfig $config): array
{
$sectors = $this->calculateSectors($symbols, $image->getWidth(), $image->getHeight());
$coordinators = [];
foreach ($symbols->getSuccess() as $number => $symbol) {
$coordinators[] = $this->processingStringSymbol($image, $symbol, $config, $sectors->random());
}
if (!empty($symbols->getFakes())) {
foreach ($symbols->getFakes() as $number => $symbol) {
$this->processingStringSymbol($image, $symbol, $config, $sectors->random());
}
}
return [
'image' => $image,
'coordinators' => $coordinators
];
}
private function processingStringSymbol(Image &$image, string|int $symbol, ImageBodyConfig $config, Sector $sector): Coordinators
{
$fontSize = $config->fontSize();
if ($fontSize > $sector->getHeight()) {
$fontSize = $sector->getHeight();
}
$fontPathFile = $config->randomFont();
$fontColor = $config->randomFontColor();
$angle = $config->randomAngle();
$marginLeft = mt_rand($sector->getX(), $sector->getX() + $sector->getWidth() - $fontSize);
$marginTop = mt_rand($sector->getY(), $sector->getY() + $sector->getHeight() - $fontSize);
return $image->addText(
text: $symbol,
x: $marginLeft,
y: $marginTop,
size: $fontSize,
angle: $angle,
hexColor: $fontColor,
fontName: $fontPathFile
);
}
private function calculateSectors(Symbols $symbols, int $width, int $height): Sectors
{
$points = count($symbols->getSuccess()) + count($symbols->getFakes());
$sumFloors = 3;
$sumRooms = ceil($points / $sumFloors);
$heightFloor = floor($height / $sumFloors);
$widthFloor = floor($width / $sumRooms);
$sectors = new Sectors();
for ($floor = 0; $floor < $sumFloors; $floor++) {
$y = $heightFloor * $floor;
for ($room = 0; $room < $sumRooms; $room++) {
$sectors->add(
x: ($widthFloor * $room),
y: $y,
width: $widthFloor,
height: $heightFloor
);
}
}
return $sectors;
}
}

View File

@@ -0,0 +1,72 @@
<?php declare(strict_types=1);
namespace App\Captcha\Images;
use App\Captcha\Config\ImageHead as ImageHeadConfig;
use App\Captcha\Contracts\ImageLines;
use App\Captcha\Dto\Image as ImageDto;
use App\Captcha\Dto\ImageHead as ImageHeadDto;
use App\Captcha\Dto\Symbols;
use App\Captcha\Contracts\ImageHead;
use \App\Captcha\Contracts\Image as ImageContract;
use App\Captcha\Enums\SymbolType;
use App\Captcha\Contracts\ImageManager;
final class Head implements ImageHead
{
public function __construct(
private readonly ImageManager $imageManager,
private readonly ImageLines $imageLines
) { }
public function processing(Symbols $symbols, ImageHeadConfig $config): ImageHeadDto
{
$image = $this->imageManager->createImage($config->getWidth(), $config->getHeight());
$image->insertBackground($config->randomBackground());
$image = match ($symbols->getType()) {
SymbolType::String => $this->processingString($image, $symbols, $config),
};
if ($config->getNumberLines() > 0) {
$this->imageLines->processing(image: $image, colors: $config->getLineColors(), lines: $config->getNumberLines());
}
$image = new ImageDto(
imageBase64: $image->encode(),
width: $image->getWidth(),
height: $image->getHeight()
);
return new ImageHeadDto(image: $image);
}
private function processingString(ImageContract $image, Symbols $symbols, ImageHeadConfig $config): Image
{
$textLeftPadding = $config->getTextPaddingLeft();
$textTopPadding = $config->getTextPaddingTop();
$countSymbols = count($symbols->getSuccess());
$widthPrefix = $image->getWidth() - $textLeftPadding;
$imageHeight = $image->getHeight() - $textTopPadding;
foreach ($symbols->getSuccess() as $number => $symbol) {
$marginLeft = $textLeftPadding + $number * $widthPrefix / $countSymbols;
$image->addText(
text: $symbol,
x: intval($marginLeft),
y: $textTopPadding,
size: $this->randomFontSize($imageHeight),
angle: $config->randomAngle(),
hexColor: $config->randomFontColor(),
fontName: $config->randomFont()
);
}
return $image;
}
private function randomFontSize(int $imageHeight): int
{
return mt_rand($imageHeight - 10, $imageHeight);
}
}