|
|
@@ -4129,7 +4129,8 @@ function safeDestroyImage(&$img)
|
|
|
* - target string 可选 目标档位:1080p / 2k / 4k,默认 4k(按长边放大)
|
|
|
* - width int 可选 指定目标宽度(与 height 同时传时优先于 target)
|
|
|
* - height int 可选 指定目标高度
|
|
|
- * - format string 可选 输出格式:jpg / png / webp,默认保持原图格式(gif 转 png)
|
|
|
+ * - format string 可选 输出格式:jpg / png / webp,默认保持原图格式
|
|
|
+ * (gif 转 png;webp 带透明转 png、无透明转 jpg)
|
|
|
* - quality int 可选 输出质量 1-100,默认 95(jpg/webp 生效)
|
|
|
* - sharpen bool 可选 是否锐化,默认 true
|
|
|
* - sharpen_amount float 可选 锐化强度 0-2,默认 0.35
|
|
|
@@ -4161,6 +4162,8 @@ function upscaleImage(array $params): array
|
|
|
$srcWidth = max(1, (int)$info[0]);
|
|
|
$srcHeight = max(1, (int)$info[1]);
|
|
|
$srcType = (int)($info[2] ?? IMAGETYPE_PNG);
|
|
|
+ // WebP 默认输出格式取决于是否带透明通道(有透明转PNG保留透明,无透明转JPG控制体积)
|
|
|
+ $srcHasAlpha = $srcType === IMAGETYPE_WEBP && upscaleImageHasAlpha($binary);
|
|
|
|
|
|
// 计算目标尺寸
|
|
|
[$dstWidth, $dstHeight] = upscaleImageResolveTargetDimensions($srcWidth, $srcHeight, $params);
|
|
|
@@ -4214,6 +4217,7 @@ function upscaleImage(array $params): array
|
|
|
$params,
|
|
|
$binary,
|
|
|
$srcType,
|
|
|
+ $srcHasAlpha,
|
|
|
$srcWidth,
|
|
|
$srcHeight,
|
|
|
$dstWidth,
|
|
|
@@ -4225,6 +4229,7 @@ function upscaleImage(array $params): array
|
|
|
$params,
|
|
|
$binary,
|
|
|
$srcType,
|
|
|
+ $srcHasAlpha,
|
|
|
$srcWidth,
|
|
|
$srcHeight,
|
|
|
$dstWidth,
|
|
|
@@ -4238,6 +4243,7 @@ function upscaleImage(array $params): array
|
|
|
* @param array $params
|
|
|
* @param string $binary 图片二进制内容
|
|
|
* @param int $srcType 原图类型(IMAGETYPE_*)
|
|
|
+ * @param bool $srcHasAlpha 原图是否带透明通道(WebP 默认格式判断用)
|
|
|
* @param int $srcWidth
|
|
|
* @param int $srcHeight
|
|
|
* @param int $dstWidth
|
|
|
@@ -4249,11 +4255,18 @@ function upscaleImageWithGd(
|
|
|
array $params,
|
|
|
string $binary,
|
|
|
int $srcType,
|
|
|
+ bool $srcHasAlpha,
|
|
|
int $srcWidth,
|
|
|
int $srcHeight,
|
|
|
int $dstWidth,
|
|
|
int $dstHeight
|
|
|
): array {
|
|
|
+ // 4K 放大峰值内存较高:目标图(4B/像素) + 锐化卷积临时缓冲(4B/像素) + 源图 + 编码缓冲,
|
|
|
+ // 按需临时提升内存限制,避免超过默认 128M 报 FatalError
|
|
|
+ upscaleImageEnsureMemory(
|
|
|
+ $dstWidth * $dstHeight * 8 + $srcWidth * $srcHeight * 4 + 96 * 1024 * 1024
|
|
|
+ );
|
|
|
+
|
|
|
$srcImg = @imagecreatefromstring($binary);
|
|
|
if (!$srcImg) {
|
|
|
throw new \RuntimeException('图片解码失败');
|
|
|
@@ -4268,6 +4281,9 @@ function upscaleImageWithGd(
|
|
|
throw new \RuntimeException('图片放大失败');
|
|
|
}
|
|
|
|
|
|
+ // 缩放完成后原图不再需要,提前释放以降低内存峰值
|
|
|
+ safeDestroyImage($srcImg);
|
|
|
+
|
|
|
// 可选锐化,弥补插值放大后的发虚感
|
|
|
$sharpen = (bool)($params['sharpen'] ?? true);
|
|
|
$sharpenAmount = (float)($params['sharpen_amount'] ?? 0.35);
|
|
|
@@ -4276,7 +4292,7 @@ function upscaleImageWithGd(
|
|
|
}
|
|
|
|
|
|
// 确定输出格式并编码
|
|
|
- $format = upscaleImageResolveOutputFormat($params, $srcType);
|
|
|
+ $format = upscaleImageResolveOutputFormat($params, $srcType, $srcHasAlpha);
|
|
|
$quality = max(1, min(100, (int)($params['quality'] ?? 95)));
|
|
|
|
|
|
$encoded = upscaleImageEncode($dstImg, $format, $quality);
|
|
|
@@ -4308,6 +4324,7 @@ function upscaleImageWithGd(
|
|
|
* @param array $params
|
|
|
* @param string $binary 图片二进制内容
|
|
|
* @param int $srcType 原图类型(IMAGETYPE_*)
|
|
|
+ * @param bool $srcHasAlpha 原图是否带透明通道(WebP 默认格式判断用)
|
|
|
* @param int $srcWidth
|
|
|
* @param int $srcHeight
|
|
|
* @param int $dstWidth
|
|
|
@@ -4319,6 +4336,7 @@ function upscaleImageWithFfmpeg(
|
|
|
array $params,
|
|
|
string $binary,
|
|
|
int $srcType,
|
|
|
+ bool $srcHasAlpha,
|
|
|
int $srcWidth,
|
|
|
int $srcHeight,
|
|
|
int $dstWidth,
|
|
|
@@ -4332,7 +4350,7 @@ function upscaleImageWithFfmpeg(
|
|
|
@mkdir($tempDir, 0775, true);
|
|
|
}
|
|
|
|
|
|
- $format = upscaleImageResolveOutputFormat($params, $srcType);
|
|
|
+ $format = upscaleImageResolveOutputFormat($params, $srcType, $srcHasAlpha);
|
|
|
$ext = $format === 'jpeg' ? 'jpg' : $format;
|
|
|
$quality = max(1, min(100, (int)($params['quality'] ?? 95)));
|
|
|
|
|
|
@@ -4404,6 +4422,48 @@ function upscaleImageGdAvailable(): bool
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * 获取当前 PHP 内存限制(字节),-1 表示无限制
|
|
|
+ *
|
|
|
+ * @return int
|
|
|
+ */
|
|
|
+function upscaleImageMemoryLimitBytes(): int
|
|
|
+{
|
|
|
+ $val = trim((string)ini_get('memory_limit'));
|
|
|
+ if ($val === '' || $val === '-1') {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ $unit = strtoupper(substr($val, -1));
|
|
|
+ $num = (float)$val;
|
|
|
+ switch ($unit) {
|
|
|
+ case 'G':
|
|
|
+ return (int)($num * 1024 * 1024 * 1024);
|
|
|
+ case 'M':
|
|
|
+ return (int)($num * 1024 * 1024);
|
|
|
+ case 'K':
|
|
|
+ return (int)($num * 1024);
|
|
|
+ default:
|
|
|
+ return (int)$num;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 确保 PHP 内存限制满足图片超分需要(只升不降;已是无限制时不做处理)
|
|
|
+ *
|
|
|
+ * 4K 放大时源图解码、目标图、锐化卷积缓冲同时存在,峰值内存较大,
|
|
|
+ * 按目标尺寸估算并临时调高 memory_limit,避免 FatalError OOM。
|
|
|
+ *
|
|
|
+ * @param int $neededBytes 预估需要的内存字节数
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+function upscaleImageEnsureMemory(int $neededBytes): void
|
|
|
+{
|
|
|
+ $current = upscaleImageMemoryLimitBytes();
|
|
|
+ if ($current > 0 && $neededBytes > $current) {
|
|
|
+ @ini_set('memory_limit', (string)(int)ceil($neededBytes / (1024 * 1024)) . 'M');
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
* 检查 ffmpeg 是否可用
|
|
|
*
|
|
|
* @param string $ffmpegPath
|
|
|
@@ -4450,30 +4510,33 @@ function upscaleImageBuildFfmpegEncodeArgs(string $format, int $quality): string
|
|
|
*
|
|
|
* @param array $params
|
|
|
* @param int $srcType
|
|
|
+ * @param bool $srcHasAlpha 原图是否带透明通道
|
|
|
* @return string
|
|
|
*/
|
|
|
-function upscaleImageResolveOutputFormat(array $params, int $srcType): string
|
|
|
+function upscaleImageResolveOutputFormat(array $params, int $srcType, bool $srcHasAlpha = false): string
|
|
|
{
|
|
|
$format = strtolower((string)($params['format'] ?? ''));
|
|
|
if (in_array($format, ['jpg', 'jpeg', 'png', 'webp'], true)) {
|
|
|
return $format;
|
|
|
}
|
|
|
- return upscaleImageGuessFormatByType($srcType);
|
|
|
+ return upscaleImageGuessFormatByType($srcType, $srcHasAlpha);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 根据图片类型推断默认输出格式
|
|
|
*
|
|
|
* @param int $srcType
|
|
|
+ * @param bool $srcHasAlpha 原图是否带透明通道
|
|
|
* @return string
|
|
|
*/
|
|
|
-function upscaleImageGuessFormatByType(int $srcType): string
|
|
|
+function upscaleImageGuessFormatByType(int $srcType, bool $srcHasAlpha = false): string
|
|
|
{
|
|
|
switch ($srcType) {
|
|
|
case IMAGETYPE_PNG:
|
|
|
return 'png';
|
|
|
case IMAGETYPE_WEBP:
|
|
|
- return function_exists('imagewebp') ? 'webp' : 'jpg';
|
|
|
+ // 带透明通道的 WebP 转 PNG 保留透明,否则转 JPG 控制体积
|
|
|
+ return $srcHasAlpha ? 'png' : 'jpg';
|
|
|
case IMAGETYPE_GIF:
|
|
|
return 'png'; // GIF 转 PNG,保留透明通道
|
|
|
case IMAGETYPE_JPEG:
|
|
|
@@ -4483,6 +4546,48 @@ function upscaleImageGuessFormatByType(int $srcType): string
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * 检测图片二进制是否带透明通道(目前用于 WebP 默认输出格式判断)
|
|
|
+ *
|
|
|
+ * 通过解析容器头判断,不整图解码,速度快:
|
|
|
+ * - WebP: ALPH 块存在、VP8X 扩展头的 ALPHA 标志位、VP8L 无损格式的 alpha 标志
|
|
|
+ *
|
|
|
+ * @param string $binary
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+function upscaleImageHasAlpha(string $binary): bool
|
|
|
+{
|
|
|
+ if (strlen($binary) < 16 || substr($binary, 0, 4) !== 'RIFF' || substr($binary, 8, 4) !== 'WEBP') {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ $offset = 12;
|
|
|
+ $len = strlen($binary);
|
|
|
+ while ($offset + 8 <= $len) {
|
|
|
+ $fourcc = substr($binary, $offset, 4);
|
|
|
+ $chunkSize = (int)unpack('V', substr($binary, $offset + 4, 4))[1];
|
|
|
+
|
|
|
+ if ($fourcc === 'ALPH') {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if ($fourcc === 'VP8X' && $chunkSize >= 1) {
|
|
|
+ // VP8X 扩展头标志字节:bit4(0x10)=ALPHA
|
|
|
+ return (ord($binary[$offset + 8]) & 0x10) !== 0;
|
|
|
+ }
|
|
|
+ if ($fourcc === 'VP8L' && $chunkSize >= 1) {
|
|
|
+ // VP8L 无损格式:首字节 bit2=alpha_is_used
|
|
|
+ return ((ord($binary[$offset + 8]) >> 2) & 1) !== 0;
|
|
|
+ }
|
|
|
+ if ($chunkSize <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ // RIFF 块数据为奇数长度时补 1 字节对齐
|
|
|
+ $offset += 8 + $chunkSize + ($chunkSize % 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
* 使用卷积矩阵对图片进行锐化(C级实现,速度快)
|
|
|
*
|
|
|
* @param resource|\GdImage $img
|
|
|
@@ -4519,6 +4624,8 @@ function upscaleImageEncode($img, string $format, int $quality): ?string
|
|
|
imagejpeg($img, null, $quality);
|
|
|
break;
|
|
|
case 'png':
|
|
|
+ // 显式开启 alpha 通道保存,否则透明区域会丢失
|
|
|
+ imagesavealpha($img, true);
|
|
|
// quality 100-1 映射到 PNG 压缩级别 0-9
|
|
|
imagepng($img, null, (int)round((100 - $quality) / 10));
|
|
|
break;
|
|
|
@@ -4526,6 +4633,7 @@ function upscaleImageEncode($img, string $format, int $quality): ?string
|
|
|
if (!function_exists('imagewebp')) {
|
|
|
throw new \RuntimeException('GD扩展不支持WebP输出');
|
|
|
}
|
|
|
+ imagesavealpha($img, true);
|
|
|
imagewebp($img, null, $quality);
|
|
|
break;
|
|
|
default:
|