|
|
@@ -312,8 +312,10 @@ function uploadStreamByTos($prefix, $stream, $filename)
|
|
|
$client = new TosClient([
|
|
|
'region' => env('VOLC_REGION'),
|
|
|
'endpoint' => env('VOLC_END_POINT'),
|
|
|
- 'ak' => env('VOLC_AK'),
|
|
|
- 'sk' => env('VOLC_SK'),
|
|
|
+ // 'ak' => env('VOLC_AK'),
|
|
|
+ // 'sk' => env('VOLC_SK'),
|
|
|
+ 'ak' => 'AKLTNDRjMzdiODkwMThhNDdhZThhOTI3MDg5MDkzYzExNTg',
|
|
|
+ 'sk' => 'TXpOak4yWmxZemxrTVRVMU5EZzVNMkprT0dJMU9HSTBaR1kzWW1NMk5qaw==',
|
|
|
]);
|
|
|
|
|
|
$input = new PutObjectInput(env('VOLC_BUCKET'), "$prefix/$filename", $stream);
|
|
|
@@ -2079,3 +2081,170 @@ function handleScriptContent($originalContent) {
|
|
|
|
|
|
return $parts;
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * 远程图片压缩(尽可能保持原图 fidelity,压缩到不超过 maxBytes 字节)
|
|
|
+ * 采用有损/无损组合策略:保留原始格式尽量不失真,若需要则通过尺寸缩放和质量调节来降低体积。
|
|
|
+ *
|
|
|
+ * @param string $url 远程图片 URL
|
|
|
+ * @param int $maxBytes 最大字节数,默认 2MB
|
|
|
+ * @return string|null 返回压缩后的图片二进制数据,失败时返回 null
|
|
|
+ */
|
|
|
+function compressRemoteImageUrlToSize(string $url, int $maxBytes = 3 * 1024 * 1024): ?string
|
|
|
+{
|
|
|
+ // 1) 下载图片数据(使用 Guzzle 以避免 allow_url_fopen 依赖)
|
|
|
+ try {
|
|
|
+ $client = new Client(['timeout' => 30]);
|
|
|
+ $response = $client->get($url, ['stream' => true]);
|
|
|
+ if ($response->getStatusCode() !== 200) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ $data = $response->getBody()->getContents();
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!$data) return null;
|
|
|
+
|
|
|
+ // 2) 识别图片类型
|
|
|
+ $imgInfo = @getimagesizefromstring($data);
|
|
|
+ $mime = $imgInfo['mime'] ?? '';
|
|
|
+ // 3) 载入图像对象
|
|
|
+ $srcImg = @imagecreatefromstring($data);
|
|
|
+ if (!$srcImg) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ $origW = imagesx($srcImg);
|
|
|
+ $origH = imagesy($srcImg);
|
|
|
+
|
|
|
+ // 3.1) 内部渲染成不同格式的字符串
|
|
|
+ $render = function($srcRes, string $mimeType, int $quality) {
|
|
|
+ ob_start();
|
|
|
+ switch (strtolower($mimeType)) {
|
|
|
+ case 'image/jpeg':
|
|
|
+ case 'image/jpg':
|
|
|
+ case 'image/pjpeg':
|
|
|
+ // 输出 JPEG
|
|
|
+ imagejpeg($srcRes, null, $quality);
|
|
|
+ break;
|
|
|
+ case 'image/png':
|
|
|
+ // 将 quality 映射到 PNG 的 compression level (0-9)
|
|
|
+ $level = (int)round((100 - $quality) / 11.11);
|
|
|
+ if ($level < 0) $level = 0;
|
|
|
+ if ($level > 9) $level = 9;
|
|
|
+ imagepng($srcRes, null, $level);
|
|
|
+ break;
|
|
|
+ case 'image/webp':
|
|
|
+ if (function_exists('imagewebp')) {
|
|
|
+ imagewebp($srcRes, null, $quality);
|
|
|
+ } else {
|
|
|
+ imagejpeg($srcRes, null, $quality);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ imagejpeg($srcRes, null, $quality);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ $out = ob_get_contents();
|
|
|
+ ob_end_clean();
|
|
|
+ return $out;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 4) 尝试策略:尽量保留原图尺寸,逐步降维/降质量,直到 <= maxBytes
|
|
|
+ $tryList = [];
|
|
|
+ // 原始尺寸,尽量保留
|
|
|
+ $tryList[] = ['scale'=>1.0, 'mime'=>$mime, 'quality'=>100];
|
|
|
+ // 逐步缩小尺寸
|
|
|
+ for ($s = 0.9; $s >= 0.2; $s -= 0.1) {
|
|
|
+ $tryList[] = ['scale'=>$s, 'mime'=>$mime, 'quality'=>90];
|
|
|
+ }
|
|
|
+ // 一系列质量等级(用于 JPEG/WebP)
|
|
|
+ $qualityLevels = [95, 90, 85, 75, 60, 50, 40, 30];
|
|
|
+ foreach ($qualityLevels as $q) {
|
|
|
+ $tryList[] = ['scale'=>1.0, 'mime'=>$mime, 'quality'=>$q];
|
|
|
+ }
|
|
|
+
|
|
|
+ $bestData = null;
|
|
|
+ foreach ($tryList as $cand) {
|
|
|
+ $scale = isset($cand['scale']) ? (float)$cand['scale'] : 1.0;
|
|
|
+ $mimeT = $cand['mime'] ?? $mime;
|
|
|
+ $quality = isset($cand['quality']) ? (int)$cand['quality'] : 90;
|
|
|
+
|
|
|
+ $w = (int)round($origW * $scale);
|
|
|
+ $h = (int)round($origH * $scale);
|
|
|
+ $src = $srcImg;
|
|
|
+ $tempImg = null;
|
|
|
+ if ($scale < 1.0) {
|
|
|
+ $tempImg = imagecreatetruecolor($w, $h);
|
|
|
+ // 处理透明通道
|
|
|
+ imagealphablending($tempImg, false);
|
|
|
+ imagesavealpha($tempImg, true);
|
|
|
+ if (in_array(strtolower($mime), ['image/png','image/webp'])) {
|
|
|
+ $transparent = imagecolorallocatealpha($tempImg, 0, 0, 0, 127);
|
|
|
+ imagefill($tempImg, 0, 0, $transparent);
|
|
|
+ }
|
|
|
+ // 确保 $srcImg 有效
|
|
|
+ if (!$srcImg || (!is_resource($srcImg) && !($srcImg instanceof \GdImage))) {
|
|
|
+ safeDestroyImage($tempImg);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ imagecopyresampled($tempImg, $srcImg, 0, 0, 0, 0, $w, $h, $origW, $origH);
|
|
|
+ $src = $tempImg;
|
|
|
+ }
|
|
|
+ $imageBytes = $render($src, $mimeT, $quality);
|
|
|
+ if ($tempImg !== null) {
|
|
|
+ safeDestroyImage($tempImg);
|
|
|
+ }
|
|
|
+ if ($imageBytes !== false && strlen($imageBytes) <= $maxBytes) {
|
|
|
+ $bestData = $imageBytes;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 5) 回退策略:若仍未达到要求,尝试更大幅度降解到一个合理的小尺寸 JPEG
|
|
|
+ if ($bestData === null) {
|
|
|
+ $tmpW = max(1, (int)round($origW * 0.5));
|
|
|
+ $tmpH = max(1, (int)round($origH * 0.5));
|
|
|
+ $tmpImg = imagecreatetruecolor($tmpW, $tmpH);
|
|
|
+ imagealphablending($tmpImg, false);
|
|
|
+ imagesavealpha($tmpImg, true);
|
|
|
+ if (in_array(strtolower($mime), ['image/png','image/webp'])) {
|
|
|
+ $transparent = imagecolorallocatealpha($tmpImg, 0, 0, 0, 127);
|
|
|
+ imagefill($tmpImg, 0, 0, $transparent);
|
|
|
+ }
|
|
|
+ imagecopyresampled($tmpImg, $srcImg, 0, 0, 0, 0, $tmpW, $tmpH, $origW, $origH);
|
|
|
+ ob_start();
|
|
|
+ if (in_array(strtolower($mime), ['image/jpeg','image/jpg','image/pjpeg'])) {
|
|
|
+ imagejpeg($tmpImg, null, 75);
|
|
|
+ } elseif (strtolower($mime) === 'image/png') {
|
|
|
+ imagepng($tmpImg, null, 6);
|
|
|
+ } elseif (function_exists('imagewebp')) {
|
|
|
+ imagewebp($tmpImg, null, 75);
|
|
|
+ } else {
|
|
|
+ imagejpeg($tmpImg, null, 75);
|
|
|
+ }
|
|
|
+ $tmpBytes = ob_get_contents();
|
|
|
+ ob_end_clean();
|
|
|
+ safeDestroyImage($tmpImg);
|
|
|
+ if ($tmpBytes !== '' && strlen($tmpBytes) <= $maxBytes) {
|
|
|
+ $bestData = $tmpBytes;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6) 清理
|
|
|
+ safeDestroyImage($srcImg);
|
|
|
+ return $bestData;
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 安全销毁 GD 图像资源(兼容 PHP7.4+ 的资源管理)
|
|
|
+ * 通过引用传递并在销毁后置空变量,避免未定义变量的问题
|
|
|
+ *
|
|
|
+ * @param resource|\GdImage|null &$img GD 图像资源或对象(PHP7.4 为 resource,PHP8.0+ 为 GdImage)
|
|
|
+ */
|
|
|
+function safeDestroyImage(&$img)
|
|
|
+{
|
|
|
+ if (is_resource($img) || (is_object($img) && $img instanceof \GdImage)) {
|
|
|
+ @imagedestroy($img);
|
|
|
+ }
|
|
|
+ $img = null;
|
|
|
+}
|