Skip to content

Commit

Permalink
Refactoring, optim, avif support, png support
Browse files Browse the repository at this point in the history
  • Loading branch information
Gappa committed Feb 8, 2023
1 parent bca2462 commit e7669a4
Show file tree
Hide file tree
Showing 10 changed files with 502 additions and 119 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"nette/http": "^3.1",
"nette/schema": "^1.0",
"nette/utils": "^3.2 || ^4.0",
"imagine/imagine": "^1.2.3"
"imagine/imagine": "^1.3.3"
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
Expand Down
11 changes: 10 additions & 1 deletion src/DI/ResizerConfig.php → src/DI/ResizerConfigDTO.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,25 @@

namespace Nelson\Resizer\DI;

final class ResizerConfig
final class ResizerConfigDTO
{
/** @var string 'Gd'|'Imagick'|'Gmagick' */
public string $library;
public bool $interlace = true;
public string $wwwDir;
public string $tempDir;
public string $cache = '/resizer/';
public bool $upgradeJpg2Webp = true;
public bool $upgradePng2Webp = true;
public bool $upgradeJpg2Avif = true;
public bool $upgradePng2Avif = true;
public bool $isWebpSupportedByServer = false;
public bool $isAvifSupportedByServer = false;
public bool $strip = true;

/** @var int<0, 100> */
public int $qualityAvif;

/** @var int<0, 100> */
public int $qualityWebp;

Expand Down
39 changes: 30 additions & 9 deletions src/DI/ResizerExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
use Imagick;
use Latte\Engine;
use Nelson\Resizer\Latte\ResizerExtension as LatteResizerExtension;
use Nelson\Resizer\OutputFormat;
use Nelson\Resizer\Resizer;
use Nelson\Resizer\ResizerConfig;
use Nette\Application\IPresenterFactory;
use Nette\DI\CompilerExtension;
use Nette\DI\Definitions\Definition;
Expand All @@ -26,7 +28,7 @@ final class ResizerExtension extends CompilerExtension

public function getConfigSchema(): Schema
{
return Expect::from(new ResizerConfig, [
return Expect::from(new ResizerConfigDTO, [
'library' => Expect::anyOf('Gd', 'Imagick', 'Gmagick')->default('Imagick'),
'qualityWebp' => Expect::int(75)->min(0)->max(100),
'qualityJpeg' => Expect::int(75)->min(0)->max(100),
Expand All @@ -38,13 +40,20 @@ public function getConfigSchema(): Schema
public function loadConfiguration(): void
{
$builder = $this->getContainerBuilder();
/** @var ResizerConfig $config */
/** @var ResizerConfigDTO $config */
$config = $this->getConfig();
$config->isWebpSupportedByServer = $this->isWebpSupported($config);
$config->isAvifSupportedByServer = $this->isAvifSupported($config);

$builder->addDefinition($this->prefix('config'))
->setFactory(ResizerConfig::class)
->setArgument('config', $config);

$builder->addDefinition($this->prefix('output.format'))
->setFactory(OutputFormat::class);

$builder->addDefinition($this->prefix('default'))
->setType(Resizer::class)
->setArgument('config', $config)
->setArgument('isWebpSupportedByServer', $this->isWebpSupported($config));
->setType(Resizer::class);
}


Expand Down Expand Up @@ -76,28 +85,40 @@ public static function getResizerLink(?bool $absolute = true): string
}


private function isWebpSupported(ResizerConfig $config): bool
private function isFormatSupported(ResizerConfigDTO $config, string $gd, string $imagick, string $gmagick): bool
{
$support = false;

switch ($config->library) {
case 'Gd':
$support = function_exists('gd_info') && !empty(gd_info()['WebP Support']);
$support = function_exists('gd_info') && !empty(gd_info()[$gd]);
break;

case 'Imagick':
$support = extension_loaded('imagick') && in_array('WEBP', Imagick::queryFormats(), true);
$support = extension_loaded('imagick') && in_array($imagick, Imagick::queryFormats(), true);
break;

case 'Gmagick':
$support = extension_loaded('gmagick') && in_array('WEBP', (new Gmagick)->queryformats(), true);
$support = extension_loaded('gmagick') && in_array($gmagick, (new Gmagick)->queryformats(), true);
break;
}

return $support;
}


private function isWebpSupported(ResizerConfigDTO $config): bool
{
return $this->isFormatSupported($config, 'WebP Support', 'WEBP', 'WEBP');
}


private function isAvifSupported(ResizerConfigDTO $config): bool
{
return $this->isFormatSupported($config, 'AVIF Support', 'AVIF', 'AVIF');
}


/**
* @return ServiceDefinition
* @throws Exception
Expand Down
3 changes: 0 additions & 3 deletions src/IResizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,4 @@ public function process(string $path, ?string $params, ?string $format = null):

public function getSourceImagePath(string $path): string;

public function canUpgradeJpg2Webp(): bool;

public function isWebpSupportedByServer(): bool;
}
129 changes: 129 additions & 0 deletions src/OutputFormat.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php
declare(strict_types=1);

namespace Nelson\Resizer;

use Exception;
use Nette\Http\Request;
use Nette\SmartObject;

final class OutputFormat
{
use SmartObject;

private bool $browserSupportsWebp;
private bool $browserSupportsAvif;


public function __construct(
private Request $request,
private ResizerConfig $config,
)
{
$this->browserSupportsAvif = $this->browserSupports(Resizer::MIME_TYPE_AVIF);
$this->browserSupportsWebp = $this->browserSupports(Resizer::MIME_TYPE_WEBP);
}


public function getOutputFormat(string $file, ?string $format = null): ?string
{
if ($format === null) {
$suffix = $this->getFileFormat($file);

$format = match ($suffix) {
Resizer::FORMAT_SUFFIX_JPG => $this->getOutputFormatForJpg(),
Resizer::FORMAT_SUFFIX_PNG => $this->getOutputFormatForPng(),
default => $suffix,
};
}

$this->isFormatSupported($format);
return $format;
}


private function getOutputFormatForJpg(): string
{
return match (true) {
$this->canServeAvif() && $this->config->canUpgradeJpg2Avif() => Resizer::FORMAT_SUFFIX_AVIF,
$this->canServeWebp() && $this->config->canUpgradeJpg2Webp() => Resizer::FORMAT_SUFFIX_WEBP,
default => Resizer::FORMAT_SUFFIX_JPG,
};
}


private function getOutputFormatForPng(): string
{
return match (true) {
$this->canServeAvif() && $this->config->canUpgradePng2Avif() => Resizer::FORMAT_SUFFIX_AVIF,
$this->canServeWebp() && $this->config->canUpgradePng2Webp() => Resizer::FORMAT_SUFFIX_WEBP,
default => Resizer::FORMAT_SUFFIX_PNG,
};
}


private function canServeWebp(): bool
{
dump($this->config->isWebpSupportedByServer());
return $this->config->isWebpSupportedByServer() && $this->browserSupportsWebp;
}


private function canServeAvif(): bool
{
return $this->config->isAvifSupportedByServer() && $this->browserSupportsAvif;
}


private function isFormatSupported(string $format): void
{
if (!in_array(strtolower($format), Resizer::SUPPORTED_FORMATS, true)) {
throw new Exception(sprintf(
"Format '%s' not supported (%s).",
$format, implode(', ', Resizer::SUPPORTED_FORMATS),
));
}
}


private function browserSupports(string $format): bool
{
$accept = (string) $this->request->getHeader('accept');
return str_contains($accept, $format);
}


/** @param string|array<int, string> $suffixes */
private function isFileOfFormat(string $path, string|array $suffixes): bool
{
if (is_string($suffixes)) {
$suffixes = [$suffixes];
}

$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
return in_array($ext, $suffixes, true);
}


private function isFileJpg(string $path): bool
{
return $this->isFileOfFormat($path, Resizer::FORMAT_SUFFIXES_JPG);
}


private function isFilePng(string $path): bool
{
return $this->isFileOfFormat($path, Resizer::FORMAT_SUFFIX_PNG);
}


private function getFileFormat(string $file): string
{
return match (true) {
$this->isFileJpg($file) => Resizer::FORMAT_SUFFIX_JPG,
$this->isFilePng($file) => Resizer::FORMAT_SUFFIX_PNG,
default => pathinfo($file, PATHINFO_EXTENSION),
};
}

}
40 changes: 5 additions & 35 deletions src/Presenters/ResizePresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ final class ResizePresenter extends Presenter
private IResponse $response;


public function __construct(private IResizer $resizer)
public function __construct(
private IResizer $resizer,
)
{
parent::__construct();
}
Expand All @@ -34,6 +36,7 @@ public function startup(): void
// Get rid of troublemaking headers
$this->response->setHeader('Pragma', '');
$this->response->setHeader('Cache-Control', '');
$this->getSession()->close();
}


Expand All @@ -46,7 +49,7 @@ public function actionDefault(
$image = $this->resizer->process(
$file,
$params,
$this->getOutputFormat($file, $format),
$format,
);
} catch (Exception $e) {
$this->error($e->getMessage());
Expand Down Expand Up @@ -91,37 +94,4 @@ private function getEtag(string $srcFile, string $dstFile): string
return filemtime($srcFile) . '-' . md5($dstFile);
}


private function getOutputFormat(string $file, ?string $format = null): ?string
{
if (
empty($format) &&
$this->resizer->canUpgradeJpg2Webp() &&
$this->resizer->isWebpSupportedByServer() &&
$this->browserSupportsWebp() &&
$this->isFormatJpg($this->getImageFormat($file))
) {
return 'webp';
}
return $format;
}


private function browserSupportsWebp(): bool
{
$accept = (string) $this->request->getHeader('accept');
return is_int(strpos($accept, 'image/webp'));
}


private function getImageFormat(string $path): string
{
return pathinfo($path, PATHINFO_EXTENSION);
}


private function isFormatJpg(string $format): bool
{
return in_array(strtolower($format), ['jpeg', 'jpg'], true);
}
}
Loading

0 comments on commit e7669a4

Please sign in to comment.