ImageCompressService.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. <?php
  2. namespace app\common\service;
  3. use Exception;
  4. class ImageCompressService
  5. {
  6. /**
  7. * 压缩图片
  8. * @param string $sourcePath 源文件路径
  9. * @param int $quality 压缩质量 (1-100)
  10. * @param int $maxWidth 最大宽度
  11. * @param int $maxHeight 最大高度
  12. * @return string|false 压缩后的文件路径
  13. */
  14. public static function compress($sourcePath, $quality = 80, $maxWidth = 1920, $maxHeight = 1080)
  15. {
  16. try {
  17. // 验证输入参数
  18. if (empty($sourcePath)) {
  19. throw new Exception('源文件路径不能为空');
  20. }
  21. outFileLog($sourcePath,'upload_img','$sourcePath1');
  22. // 获取图片信息
  23. $imageInfo = getimagesize($sourcePath);
  24. outFileLog($imageInfo,'upload_img','imgInfo');
  25. if (!$imageInfo) {
  26. throw new Exception('无法获取图片信息');
  27. }
  28. $width = $imageInfo[0];
  29. $height = $imageInfo[1];
  30. $type = $imageInfo[2];
  31. outFileLog($width,'upload_img','$width');
  32. outFileLog($height,'upload_img','$height');
  33. // 如果图片尺寸已经很小,且质量要求不高,直接返回原文件
  34. if ($width <= $maxWidth && $height <= $maxHeight && $quality >= 90) {
  35. return $sourcePath;
  36. }
  37. outFileLog($sourcePath,'upload_img','$sourcePath2');
  38. // 计算新尺寸
  39. $newSize = self::calculateNewSize($width, $height, $maxWidth, $maxHeight);
  40. $newWidth = $newSize['width'];
  41. $newHeight = $newSize['height'];
  42. outFileLog($newWidth,'upload_img','$newWidth');
  43. outFileLog($newHeight,'upload_img','$newHeight');
  44. // 创建源图片资源
  45. $sourceImage = self::createImageFromType($sourcePath, $type);
  46. if (!$sourceImage) {
  47. throw new Exception('不支持的图片格式');
  48. }
  49. outFileLog($sourceImage,'upload_img','$sourceImage');
  50. // 创建新图片资源
  51. $newImage = imagecreatetruecolor($newWidth, $newHeight);
  52. outFileLog($newImage,'upload_img','$newImage');
  53. // 处理透明背景(PNG/GIF)
  54. if ($type == IMAGETYPE_PNG || $type == IMAGETYPE_GIF) {
  55. imagealphablending($newImage, false);
  56. imagesavealpha($newImage, true);
  57. $transparent = imagecolorallocatealpha($newImage, 255, 255, 255, 127);
  58. imagefill($newImage, 0, 0, $transparent);
  59. }
  60. // 重新采样
  61. imagecopyresampled($newImage, $sourceImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
  62. // 生成压缩后的文件路径
  63. $compressedPath = self::generateCompressedPath($sourcePath);
  64. outFileLog($compressedPath,'upload_img','$compressedPath');
  65. // 保存压缩后的图片
  66. $result = self::saveImageByType($newImage, $compressedPath, $type, $quality);
  67. utFileLog($result,'upload_img','$result');
  68. // $imageInfo = getimagesize($result);
  69. // 释放内存
  70. imagedestroy($sourceImage);
  71. imagedestroy($newImage);
  72. return $result ? $compressedPath : false;
  73. } catch (Exception $e) {
  74. return false;
  75. }
  76. }
  77. /**
  78. * 计算新尺寸(保持宽高比)
  79. */
  80. private static function calculateNewSize($width, $height, $maxWidth, $maxHeight)
  81. {
  82. $ratio = min($maxWidth / $width, $maxHeight / $height, 1);
  83. return [
  84. 'width' => intval($width * $ratio),
  85. 'height' => intval($height * $ratio)
  86. ];
  87. }
  88. /**
  89. * 根据图片类型创建图片资源
  90. */
  91. private static function createImageFromType($path, $type)
  92. {
  93. // 检查文件是否存在
  94. if (!file_exists($path)) {
  95. throw new Exception('图片文件不存在: ' . $path);
  96. }
  97. outFileLog(file_exists($path),'upload_img','createImageFromType-$path1');
  98. // 检查文件是否可读
  99. if (!is_readable($path)) {
  100. throw new Exception('图片文件无法读取: ' . $path);
  101. }
  102. outFileLog(is_readable($path),'upload_img','createImageFromType-$path2');
  103. // 检查GD扩展
  104. if (!extension_loaded('gd')) {
  105. throw new Exception('GD扩展未安装');
  106. }
  107. outFileLog(extension_loaded('gd'),'upload_img','createImageFromType-gd');
  108. outFileLog($type,'upload_img','createImageFromType-$type');
  109. outFileLog($path,'upload_img','createImageFromType-$path');
  110. try {
  111. switch ($type) {
  112. case IMAGETYPE_JPEG:
  113. // PNG特殊处理
  114. outFileLog('开始处理PNG图片: ' . $path, 'upload_img', 'png_process_start');
  115. // 检查文件大小
  116. $fileSize = filesize($path);
  117. outFileLog('PNG文件大小: ' . $fileSize . ' bytes', 'upload_img', 'png_file_size');
  118. // 检查内存限制
  119. $memoryLimit = ini_get('memory_limit');
  120. $currentMemory = memory_get_usage(true);
  121. outFileLog('内存限制: ' . $memoryLimit . ', 当前使用: ' . $currentMemory, 'upload_img', 'memory_info');
  122. $image = imagecreatefromjpeg($path);
  123. outFileLog($image, 'upload_img', '$image112');
  124. if ($image === false) {
  125. // 获取详细错误信息
  126. $error = error_get_last();
  127. outFileLog('PNG创建失败,错误信息: ' . json_encode($error), 'upload_img', 'png_create_error');
  128. // 尝试使用getimagesize验证
  129. $imageInfo = @getimagesize($path);
  130. if ($imageInfo === false) {
  131. throw new Exception('PNG文件无法被识别,可能已损坏');
  132. }
  133. outFileLog('PNG图片信息: ' . json_encode($imageInfo), 'upload_img', 'png_image_info');
  134. }
  135. break;
  136. case IMAGETYPE_PNG:
  137. // PNG特殊处理
  138. outFileLog('开始处理PNG图片: ' . $path, 'upload_img', 'png_process_start');
  139. // 检查文件大小
  140. $fileSize = filesize($path);
  141. outFileLog('PNG文件大小: ' . $fileSize . ' bytes', 'upload_img', 'png_file_size');
  142. // 检查内存限制
  143. $memoryLimit = ini_get('memory_limit');
  144. $currentMemory = memory_get_usage(true);
  145. outFileLog('内存限制: ' . $memoryLimit . ', 当前使用: ' . $currentMemory, 'upload_img', 'memory_info');
  146. // 尝试创建PNG图片资源
  147. $image = @imagecreatefrompng($path);
  148. // $image = imagecreatefrompng($path);
  149. if ($image === false) {
  150. // 获取详细错误信息
  151. $error = error_get_last();
  152. outFileLog('PNG创建失败,错误信息: ' . json_encode($error), 'upload_img', 'png_create_error');
  153. // 尝试验证PNG文件头
  154. $handle = fopen($path, 'rb');
  155. $header = fread($handle, 8);
  156. fclose($handle);
  157. $pngSignature = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
  158. if ($header !== $pngSignature) {
  159. throw new Exception('PNG文件头不正确,文件可能损坏');
  160. }
  161. // 尝试使用getimagesize验证
  162. $imageInfo = @getimagesize($path);
  163. if ($imageInfo === false) {
  164. throw new Exception('PNG文件无法被识别,可能已损坏');
  165. }
  166. outFileLog('PNG图片信息: ' . json_encode($imageInfo), 'upload_img', 'png_image_info');
  167. }
  168. break;
  169. case IMAGETYPE_GIF:
  170. $image = imagecreatefromgif($path);
  171. break;
  172. case IMAGETYPE_WEBP:
  173. if (!function_exists('imagecreatefromwebp')) {
  174. throw new Exception('WebP格式不支持');
  175. }
  176. $image = imagecreatefromwebp($path);
  177. break;
  178. default:
  179. throw new Exception('不支持的图片格式: ' . $type);
  180. }
  181. if (!$image) {
  182. throw new Exception('创建图片资源失败,可能是内存不足或文件损坏');
  183. }
  184. outFileLog($image ? 'success' : 'failed', 'upload_img', 'createImageFromType-result');
  185. return $image;
  186. } catch (Exception $e) {
  187. outFileLog('图片处理异常: ' . $e->getMessage(), 'upload_img', 'createImageFromType-exception');
  188. throw new Exception('图片处理失败: ' . $e->getMessage());
  189. }
  190. }
  191. /**
  192. * 根据图片类型保存图片
  193. */
  194. private static function saveImageByType($image, $path, $type, $quality)
  195. {
  196. switch ($type) {
  197. case IMAGETYPE_JPEG:
  198. return imagejpeg($image, $path, $quality);
  199. case IMAGETYPE_PNG:
  200. // PNG压缩级别 0-9,质量参数需要转换
  201. $pngQuality = intval((100 - $quality) / 10);
  202. return imagepng($image, $path, $pngQuality);
  203. case IMAGETYPE_GIF:
  204. return imagegif($image, $path);
  205. case IMAGETYPE_WEBP:
  206. return imagewebp($image, $path, $quality);
  207. default:
  208. return false;
  209. }
  210. }
  211. /**
  212. * 生成压缩后的文件路径
  213. */
  214. private static function generateCompressedPath($originalPath)
  215. {
  216. $pathInfo = pathinfo($originalPath);
  217. return $pathInfo['dirname'] . '/' . $pathInfo['filename'] . '_compressed.' . $pathInfo['extension'];
  218. }
  219. }