WebOrbiton
v2.0.0.0

Publisium

119 lines · 3.2 KB
  1. <?php
  2. ​
  3. declare(strict_types=1);
  4. ​
  5. final class Webp
  6. {
  7. private const QUALITY = 82;
  8. ​
  9. public static function supported(): bool
  10. {
  11. return function_exists('imagewebp') && function_exists('imagecreatefromjpeg') && function_exists('imagecreatefrompng');
  12. }
  13. ​
  14. public static function convert(string $path, string $mimeType): ?string
  15. {
  16. if (!self::supported() || !in_array($mimeType, ['image/jpeg', 'image/png'], true)) {
  17. return null;
  18. }
  19. ​
  20. $size = @getimagesize($path);
  21. if ($size === false || !self::fitsInMemory((int) $size[0], (int) $size[1])) {
  22. return null;
  23. }
  24. ​
  25. $orientation = 1;
  26. if ($mimeType === 'image/jpeg') {
  27. if (!function_exists('exif_read_data')) {
  28. return null;
  29. }
  30. $exif = @exif_read_data($path);
  31. $orientation = is_array($exif) ? (int) ($exif['Orientation'] ?? 1) : 1;
  32. }
  33. ​
  34. $image = $mimeType === 'image/jpeg' ? @imagecreatefromjpeg($path) : @imagecreatefrompng($path);
  35. if ($image === false) {
  36. return null;
  37. }
  38. ​
  39. if ($mimeType === 'image/png') {
  40. imagepalettetotruecolor($image);
  41. imagealphablending($image, false);
  42. imagesavealpha($image, true);
  43. }
  44. ​
  45. $image = self::applyOrientation($image, $orientation);
  46. ​
  47. $target = (string) preg_replace('/\.[A-Za-z0-9]+$/', '', $path) . '.webp';
  48. $saved = @imagewebp($image, $target, self::QUALITY);
  49. imagedestroy($image);
  50. ​
  51. clearstatcache(true, $target);
  52. if (!$saved || !is_file($target) || filesize($target) === 0 || filesize($target) >= filesize($path)) {
  53. @unlink($target);
  54. return null;
  55. }
  56. ​
  57. return $target;
  58. }
  59. ​
  60. private static function applyOrientation(GdImage $image, int $orientation): GdImage
  61. {
  62. if (in_array($orientation, [4, 5, 7], true)) {
  63. imageflip($image, IMG_FLIP_VERTICAL);
  64. } elseif ($orientation === 2) {
  65. imageflip($image, IMG_FLIP_HORIZONTAL);
  66. }
  67. ​
  68. $angle = match ($orientation) {
  69. 3 => 180,
  70. 5, 6 => -90,
  71. 7, 8 => 90,
  72. default => 0,
  73. };
  74. ​
  75. if ($angle === 0) {
  76. return $image;
  77. }
  78. ​
  79. $rotated = imagerotate($image, $angle, 0);
  80. if ($rotated === false) {
  81. return $image;
  82. }
  83. imagedestroy($image);
  84. ​
  85. return $rotated;
  86. }
  87. ​
  88. private static function fitsInMemory(int $width, int $height): bool
  89. {
  90. if ($width <= 0 || $height <= 0) {
  91. return false;
  92. }
  93. ​
  94. $limit = self::memoryLimitBytes();
  95. if ($limit <= 0) {
  96. return true;
  97. }
  98. ​
  99. return memory_get_usage() + $width * $height * 10 < $limit;
  100. }
  101. ​
  102. private static function memoryLimitBytes(): int
  103. {
  104. $value = trim((string) ini_get('memory_limit'));
  105. if ($value === '' || $value === '-1') {
  106. return -1;
  107. }
  108. ​
  109. $number = (int) $value;
  110. ​
  111. return match (strtolower(substr($value, -1))) {
  112. 'g' => $number * 1024 * 1024 * 1024,
  113. 'm' => $number * 1024 * 1024,
  114. 'k' => $number * 1024,
  115. default => $number,
  116. };
  117. }
  118. }
  119. ​