2026-07-26 11:16:00 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
use App\Services\Advertising\BannerGenerator;
|
|
|
|
|
|
|
|
|
|
/** Хелпер: исходная JPEG-картинка заданного размера (сплошной цвет) как бинарь. */
|
|
|
|
|
function srcJpeg(int $w, int $h): string
|
|
|
|
|
{
|
|
|
|
|
$img = imagecreatetruecolor($w, $h);
|
|
|
|
|
imagefill($img, 0, 0, imagecolorallocate($img, 15, 110, 86));
|
|
|
|
|
ob_start();
|
|
|
|
|
imagejpeg($img, null, 90);
|
|
|
|
|
$bin = (string) ob_get_clean();
|
|
|
|
|
imagedestroy($img);
|
|
|
|
|
|
|
|
|
|
return $bin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it('делает JPEG ТОЧНОГО целевого размера из большой квадратной исходной', function () {
|
2026-07-28 07:21:02 +03:00
|
|
|
$out = (new BannerGenerator)->coverJpeg(srcJpeg(2000, 2000), 300, 250);
|
2026-07-26 11:16:00 +03:00
|
|
|
|
|
|
|
|
$info = getimagesizefromstring($out);
|
|
|
|
|
expect($info)->not->toBeFalse()
|
|
|
|
|
->and($info[0])->toBe(300)
|
|
|
|
|
->and($info[1])->toBe(250)
|
|
|
|
|
->and($info['mime'])->toBe('image/jpeg')
|
|
|
|
|
->and(strlen($out))->toBeLessThanOrEqual(512000);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('работает для широкого и высокого форматов (cover, точные пиксели)', function () {
|
2026-07-28 07:21:02 +03:00
|
|
|
$gen = new BannerGenerator;
|
2026-07-26 11:16:00 +03:00
|
|
|
|
|
|
|
|
$wide = getimagesizefromstring($gen->coverJpeg(srcJpeg(1200, 1200), 728, 90));
|
|
|
|
|
expect($wide[0])->toBe(728)->and($wide[1])->toBe(90);
|
|
|
|
|
|
|
|
|
|
$tall = getimagesizefromstring($gen->coverJpeg(srcJpeg(1200, 1200), 240, 600));
|
|
|
|
|
expect($tall[0])->toBe(240)->and($tall[1])->toBe(600);
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-28 07:21:02 +03:00
|
|
|
/** Хелпер: «шумная» картинка — такую JPEG не ужимает почти никак, вес остаётся большим. */
|
|
|
|
|
function noisyJpeg(int $w, int $h): string
|
|
|
|
|
{
|
|
|
|
|
$img = imagecreatetruecolor($w, $h);
|
|
|
|
|
for ($x = 0; $x < $w; $x += 2) {
|
|
|
|
|
for ($y = 0; $y < $h; $y += 2) {
|
|
|
|
|
imagesetpixel($img, $x, $y, imagecolorallocate($img, ($x * 7) % 255, ($y * 13) % 255, ($x * $y) % 255));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ob_start();
|
|
|
|
|
imagejpeg($img, null, 95);
|
|
|
|
|
$bin = (string) ob_get_clean();
|
|
|
|
|
imagedestroy($img);
|
|
|
|
|
|
|
|
|
|
return $bin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Подбор качества упирается в пол 40 и дальше жать не может. Раньше в этом случае наружу
|
|
|
|
|
* молча уходил файл любого веса — а кабинет Яндекса тяжелее предела его не примет, и узнали
|
|
|
|
|
* бы мы об этом уже роботом, стоящим перед окном загрузки на боевом. Лучше честно сказать,
|
|
|
|
|
* что картинка не ужимается, чем отдать заведомо негодный файл.
|
|
|
|
|
*/
|
|
|
|
|
it('не отдаёт молча файл тяжелее предела, а честно отказывается', function () {
|
|
|
|
|
expect(fn () => (new BannerGenerator)->coverJpeg(noisyJpeg(1200, 1200), 970, 250, 2000))
|
|
|
|
|
->toThrow(RuntimeException::class);
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-26 11:16:00 +03:00
|
|
|
it('бросает исключение на нечитаемой картинке', function () {
|
2026-07-28 07:21:02 +03:00
|
|
|
expect(fn () => (new BannerGenerator)->coverJpeg('не картинка', 300, 250))
|
2026-07-26 11:16:00 +03:00
|
|
|
->toThrow(RuntimeException::class);
|
|
|
|
|
});
|