| | |
| | | private function savePoster($backdrop, $avatarUrl, $imageUrl, $logo) |
| | | { |
| | | |
| | | // 创建画布 |
| | | list($width, $height) = getimagesize($backdrop); |
| | | // 获取背景图信息,判断图片类型 |
| | | $backdropInfo = getimagesize($backdrop); |
| | | $width = $backdropInfo[0]; |
| | | $height = $backdropInfo[1]; |
| | | |
| | | // 根据原图类型创建对应类型的画布,提高质量 |
| | | $imageType = $backdropInfo[2]; |
| | | |
| | | // 创建真彩色画布,确保足够的色彩深度 |
| | | $newImage = imagecreatetruecolor($width, $height); |
| | | $backdropIm = $this->imagEcr($backdrop); |
| | | |
| | | // 启用抗锯齿 |
| | | imageantialias($newImage, true); |
| | | // 将第一张图片覆盖在新图像上 |
| | | imagecopy($newImage, $backdropIm, 0, 0, 0, 0, $width, $height); |
| | | |
| | | // 启用alpha混合模式,确保透明度正常 |
| | | imagealphablending($newImage, true); |
| | | |
| | | // 保存透明度 |
| | | imagesavealpha($newImage, true); |
| | | |
| | | // 加载背景图 |
| | | $backdropIm = $this->imagEcr($backdrop); |
| | | |
| | | // 将背景图复制到画布(使用更好的插值方法) |
| | | imagecopyresampled($newImage, $backdropIm, 0, 0, 0, 0, $width, $height, $width, $height); |
| | | if (!empty($this->config['icon'])) { |
| | | foreach ($this->config['icon'] as $key => $value) { |
| | | $this->addImagecopy($newImage, $value); |
| | |
| | | } |
| | | // 脱敏处理 |
| | | if($this->businessName=='desensitization'){ |
| | | // 保存原始数据以便后续可能需要使用 |
| | | $originalDealer = $this->dealer; |
| | | |
| | | // 手机号脱敏:保留前3位和后4位 |
| | | if(!empty($this->dealer['mobile'])){ |
| | | $this->dealer['mobile'] = $this->maskPhoneNumber($this->dealer['mobile']); |
| | |
| | | $avatarY = $this->config['avatar']['top']; |
| | | // 打开用户头像 |
| | | $avatarIm = $this->imagEcr($avatarUrl); |
| | | // 使用更好的插值方法复制头像(使用imagecopy保持原有质量) |
| | | imagecopy($newImage, $avatarIm, $avatarX, $avatarY, 0, 0, $avatarWidth, $avatarWidth); |
| | | } |
| | | if (is_file($logo) && $this->config['logo']['display'] == 1) { |
| | |
| | | $logoY = $this->config['logo']['top']; |
| | | // 打开用户logo |
| | | $logoImage = $this->imagEcr($logo); |
| | | // 使用更好的插值方法复制logo(使用imagecopy保持原有质量) |
| | | imagecopy($newImage, $logoImage, $logoX, $logoY, 0, 0, $logoWidth, $logoHeight); |
| | | } |
| | | // 写入用户昵称 |
| | | $this->addText($newImage, 'name'); |
| | | $this->addText($newImage, 'name', '', 0, false, $width); |
| | | // 写入公司列表 |
| | | $unitBaseTop = 0; |
| | | $unitFontSize = $this->config['unit'][0]['fontSize']; |
| | | foreach ($this->dealer['unit'] as $key => $value) { |
| | | // 写入公司 |
| | | $this->addText($newImage, 'unit', '', $key, true); |
| | | if($key > 0){ |
| | | $this->config['unit'][$key] = $this->config['unit'][0]; |
| | | // 计算偏移量:每个公司增加基于前一个公司实际行数的距离 |
| | | $this->config['unit'][$key]['top'] = $this->config['unit'][0]['top'] + $unitBaseTop; |
| | | } |
| | | // 写入公司,获取实际占用的行数 |
| | | $lineCount = $this->addText($newImage, 'unit', '', $key, true, $width); |
| | | // 计算下一个公司的偏移量:(行数 - 1) * 行高 + 行间距 |
| | | $unitLineHeight = $this->getLineHeight($unitFontSize); |
| | | $unitLineSpacing = $this->getLineSpacing($unitFontSize); |
| | | if ($key > 0) { |
| | | $unitBaseTop += ($lineCount * $unitLineHeight) + $unitLineSpacing; |
| | | } else { |
| | | $unitBaseTop = ($lineCount * $unitLineHeight) + $unitLineSpacing; |
| | | } |
| | | } |
| | | // 写入职位列表 |
| | | if (!empty($this->dealer['duties']) && !empty($this->config['duties'])) { |
| | | // 写入职位 |
| | | $this->addText($newImage, 'duties'); |
| | | $dutiesBaseTop = 0; |
| | | $dutiesFontSize = $this->config['duties'][0]['fontSize']; |
| | | foreach ($this->dealer['duties'] as $key => $value) { |
| | | if($key > 0){ |
| | | $this->config['duties'][$key] = $this->config['duties'][0]; |
| | | // 计算偏移量:每个职位增加基于前一个职位实际行数的距离 |
| | | $this->config['duties'][$key]['top'] = $this->config['duties'][0]['top'] + $dutiesBaseTop; |
| | | } |
| | | // 写入职位,获取实际占用的行数 |
| | | $lineCount = $this->addText($newImage, 'duties','', $key, true, $width); |
| | | // 计算下一个职位的偏移量:(行数 - 1) * 行高 + 行间距 |
| | | $dutiesLineHeight = $this->getLineHeight($dutiesFontSize); |
| | | $dutiesLineSpacing = $this->getLineSpacing($dutiesFontSize); |
| | | if ($key > 0) { |
| | | $dutiesBaseTop += ($lineCount * $dutiesLineHeight) + $dutiesLineSpacing; |
| | | } else { |
| | | $dutiesBaseTop = ($lineCount * $dutiesLineHeight) + $dutiesLineSpacing; |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // 写入position列表(备用字段) |
| | | if (!empty($this->dealer['position']) && !empty($this->config['position'])) { |
| | | $positionBaseTop = 0; |
| | | $positionFontSize = $this->config['position'][0]['fontSize']; |
| | | foreach ($this->dealer['position'] as $key => $value) { |
| | | // 写入公司 |
| | | $this->addText($newImage, 'position', '', $key, true); |
| | | if($key > 0){ |
| | | $this->config['position'][$key] = $this->config['position'][0]; |
| | | // 计算偏移量:每个position增加基于前一个实际行数的距离 |
| | | $this->config['position'][$key]['top'] = $this->config['position'][0]['top'] + $positionBaseTop; |
| | | } |
| | | // 写入position,获取实际占用的行数 |
| | | $lineCount = $this->addText($newImage, 'position', '', $key, true, $width); |
| | | // 计算下一个position的偏移量:(行数 - 1) * 行高 + 行间距 |
| | | $positionLineHeight = $this->getLineHeight($positionFontSize); |
| | | $positionLineSpacing = $this->getLineSpacing($positionFontSize); |
| | | if ($key > 0) { |
| | | $positionBaseTop += ($lineCount * $positionLineHeight) + $positionLineSpacing; |
| | | } else { |
| | | $positionBaseTop = ($lineCount * $positionLineHeight) + $positionLineSpacing; |
| | | } |
| | | } |
| | | } |
| | | // 写入手机号 |
| | | $this->addText($newImage, 'mobile', '手机:'); |
| | | $this->addText($newImage, 'mobile', '手机:', 0, false, $width); |
| | | if (!empty($this->dealer['wechat']) && !empty($this->config['wechat'])) { |
| | | // 写入微信 |
| | | $this->addText($newImage, 'wechat', '微信:'); |
| | | $this->addText($newImage, 'wechat', '微信:', 0, false, $width); |
| | | } |
| | | // 写入邮箱 |
| | | if ($this->dealer['mailbox']) { |
| | | // 写入邮箱 |
| | | $this->addText($newImage, 'mailbox', '邮箱:'); |
| | | $this->addText($newImage, 'mailbox', '邮箱:', 0, false, $width); |
| | | } |
| | | if ($this->dealer['phone']) { |
| | | $this->addText($newImage, 'phone', '电话:'); |
| | | $this->addText($newImage, 'phone', '电话:', 0, false, $width); |
| | | } |
| | | // 保存图片 |
| | | imagejpeg($newImage, $this->getPosterPath($imageUrl),100); // 根据需要选择合适的函数(如imagepng、imagegif等) |
| | | // 根据背景图类型选择保存格式 |
| | | $imageType = $backdropInfo[2]; |
| | | $savePath = $this->getPosterPath($imageUrl); |
| | | |
| | | if ($imageType == IMAGETYPE_PNG) { |
| | | // PNG格式,使用最高质量(9) |
| | | imagepng($newImage, $savePath, 9); |
| | | } elseif ($imageType == IMAGETYPE_GIF) { |
| | | // GIF格式 |
| | | imagegif($newImage, $savePath); |
| | | } else { |
| | | // JPEG格式,使用最高质量(100) |
| | | imagejpeg($newImage, $savePath, 100); |
| | | } |
| | | |
| | | // 清理内存 |
| | | imagedestroy($newImage); |
| | | imagedestroy($backdropIm); |
| | | return $this->getPosterUrl($imageUrl); |
| | | } |
| | | |
| | |
| | | imagecopyresampled($targetImage, $image, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height); // 保存时使用最高质量 |
| | | $ext = pathinfo($imageUrl, PATHINFO_EXTENSION); |
| | | if ($ext == 'png'){ |
| | | imagepng($targetImage, $imageUrl,0); // 根据需要选择合适的函数(如imagepng、imagegif等) |
| | | imagepng($targetImage, $imageUrl, 9); // PNG使用最高质量(9) |
| | | }else{ |
| | | imagejpeg($targetImage, $imageUrl,100); |
| | | imagejpeg($targetImage, $imageUrl, 100); // JPEG使用最高质量 |
| | | } |
| | | |
| | | // 清理内存 |
| | |
| | | * @param $key |
| | | * @param $type |
| | | * @param $width |
| | | * @return array|bool |
| | | * @return array|bool|int 返回实际占用的行数(仅对type=true时有效) |
| | | */ |
| | | public function addText($editor, $name, $text = '', $key = 0, $type = false, $width = 0) |
| | | { |
| | | $fontPath = $_SERVER['DOCUMENT_ROOT']. '/fonts/MSYH.TTC'; |
| | | if ($type) { |
| | | list($fontSize, $fontX,$fontY) = self::SizeLeftTop($this->config[$name][$key]['fontSize'], $this->config[$name][$key]['left'],$this->config[$name][$key]['top']); |
| | | $colorResource=self::colorResource($this->config[$name][$key]['color'],$editor); |
| | | return imagettftext($editor, $fontSize, 0, $fontX, $fontY, $colorResource, $fontPath, $text . $this->dealer[$name][$key]); |
| | | // 确保配置数组存在该索引,不存在则使用索引0 |
| | | $configKey = isset($this->config[$name][$key]) ? $key : 0; |
| | | list($fontSize, $fontX,$fontY) = self::SizeLeftTop($this->config[$name][$configKey]['fontSize'], $this->config[$name][$configKey]['left'],$this->config[$name][$configKey]['top']); |
| | | $colorResource=self::colorResource($this->config[$name][$configKey]['color'],$editor); |
| | | // 智能计算最大宽度 |
| | | $maxWidth = $this->calculateFieldMaxWidth($this->config[$name][$configKey]['left'], $this->config[$name][$configKey]['top'], $width); |
| | | // 写入支持换行的文本 |
| | | return $this->writeTextWithWrap($editor, $text . $this->dealer[$name][$key], $fontX, $fontY, $fontSize, $colorResource, $fontPath, $maxWidth); |
| | | } else if ($name == 'mobile') { |
| | | list($fontSize, $fontX,$fontY) = self::SizeLeftTop($this->config[$name]['fontSize'], $this->config[$name]['left'],$this->config[$name]['top']); |
| | | $text = $text . $this->dealer['mobile']; |
| | | |
| | | |
| | | if (!empty($this->dealer['mobile_phone'])) { |
| | | $text = $text . ' / ' . $this->dealer['mobile_phone']; |
| | | } |
| | | $colorResource=self::colorResource($this->config[$name]['color'],$editor); |
| | | return imagettftext($editor, $fontSize, 0, $fontX, $fontY, $colorResource, $fontPath, $text); |
| | | // 智能计算最大宽度 |
| | | $maxWidth = $this->calculateFieldMaxWidth($this->config[$name]['left'], $this->config[$name]['top'], $width); |
| | | // 写入支持换行的文本 |
| | | $this->writeTextWithWrap($editor, $text, $fontX, $fontY, $fontSize, $colorResource, $fontPath, $maxWidth); |
| | | } else if ($name == 'duties') { |
| | | list($fontSize, $fontX,$fontY) = self::SizeLeftTop($this->config[$name][$key]['fontSize'], $this->config[$name][$key]['left'],$this->config[$name][$key]['top']); |
| | | // 确保配置数组存在该索引,不存在则使用索引0 |
| | | $configKey = isset($this->config[$name][$key]) ? $key : 0; |
| | | list($fontSize, $fontX,$fontY) = self::SizeLeftTop($this->config[$name][$configKey]['fontSize'], $this->config[$name][$configKey]['left'],$this->config[$name][$configKey]['top']); |
| | | $duties = $this->dealer['duties'][$key]; |
| | | if (!empty($this->dealer['duties'][1])) { |
| | | $duties = $duties . ' / ' . $this->dealer['duties'][1]; |
| | | } |
| | | if (!empty($this->dealer['duties'][2])) { |
| | | $duties = $duties . ' / ' . $this->dealer['duties'][2]; |
| | | } |
| | | $colorResource=self::colorResource($this->config['duties'][0]['color'],$editor); |
| | | return imagettftext($editor, $fontSize, 0, $fontX, $fontY, $colorResource, $fontPath, $text . $duties); |
| | | // 智能计算最大宽度 |
| | | $maxWidth = $this->calculateFieldMaxWidth($this->config[$name][$configKey]['left'], $this->config[$name][$configKey]['top'], $width); |
| | | // 写入支持换行的文本 |
| | | return $this->writeTextWithWrap($editor, $text . $duties, $fontX, $fontY, $fontSize, $colorResource, $fontPath, $maxWidth); |
| | | } else if ($name == 'address') { |
| | | list($fontSize, $fontX,$fontY) = self::SizeLeftTop($this->config[$name][$key]['fontSize'], $this->config[$name][$key]['left'],$this->config[$name][$key]['top']); |
| | | $title = $this->dealer['address'][0]; |
| | |
| | | } else { |
| | | list($fontSize, $fontX,$fontY) = self::SizeLeftTop($this->config[$name]['fontSize'], $this->config[$name]['left'],$this->config[$name]['top']); |
| | | $colorResource=self::colorResource($this->config[$name]['color'],$editor); |
| | | return imagettftext($editor, $fontSize, 0, $fontX, $fontY, $colorResource, $fontPath, $text . $this->dealer[$name]); // path/to/font.ttf为自定义字体路径 |
| | | // 智能计算最大宽度 |
| | | $maxWidth = $this->calculateFieldMaxWidth($this->config[$name]['left'], $this->config[$name]['top'], $width); |
| | | // 写入支持换行的文本 |
| | | $this->writeTextWithWrap($editor, $text . $this->dealer[$name], $fontX, $fontY, $fontSize, $colorResource, $fontPath, $maxWidth); |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | |
| | | $areaCode = $parts[0]; // 区号 |
| | | $number = end($parts); // 号码部分 |
| | | $separator = strpos($phone, '-') !== false ? '-' : ' '; // 保持原始分隔符 |
| | | |
| | | |
| | | $numLen = strlen($number); |
| | | if($numLen <= 4) return $phone; // 号码太短不脱敏 |
| | | |
| | | $numPrefix = substr($number, 0, 0); // 号码部分前半段不显示 |
| | | |
| | | $numSuffix = substr($number, -4); // 保留号码后4位 |
| | | $stars = str_repeat('*', $numLen - 4); |
| | | |
| | |
| | | * @param $fontSize |
| | | * @param $left |
| | | * @param $top |
| | | * @return array |
| | | * @return array [fontSizePt, left, top] |
| | | */ |
| | | private function SizeLeftTop($fontSize,$left,$top){ |
| | | // 正确的px到pt转换系数:1px = 0.75pt |
| | | // 使用0.75而不是0.76以确保字体大小显示准确 |
| | | $data[0] = $fontSize * 0.75; |
| | | $data[1] = $left; |
| | | $data[2] = $top + $fontSize; |
| | | // px到pt转换系数:1px ≈ 0.75pt (GD库使用磅pt作为字体单位) |
| | | // 使用更精确的转换系数 |
| | | $fontSizePt = $fontSize * 0.75; |
| | | |
| | | // 调整top位置,使文字基线对齐 |
| | | // imagettftext的y坐标是文字基线位置,不是文字顶部 |
| | | // 需要加上字体大小来使文字显示在期望的位置 |
| | | $data[0] = $fontSizePt; // 字体大小(pt) |
| | | $data[1] = (float)$left; // 左边距(px) |
| | | $data[2] = (float)$top + $fontSize; // 顶部位置(px),加字体大小以校正基线 |
| | | return $data; |
| | | } |
| | | |
| | | /** |
| | | * 获取文本实际高度 |
| | | * @param $fontSizePx 字体大小(px) |
| | | * @return float 行高(px) |
| | | */ |
| | | private function getLineHeight($fontSizePx) { |
| | | // 行高 = 字体大小 * 1.5 (更舒适的行间距) |
| | | return $fontSizePx * 1.5; |
| | | } |
| | | |
| | | /** |
| | | * 获取行间距 |
| | | * @param $fontSizePx 字体大小(px) |
| | | * @return float 行间距(px) |
| | | */ |
| | | private function getLineSpacing($fontSizePx) { |
| | | // 行间距 = 字体大小 * 0.5 (50%的字体大小作为间距) |
| | | return $fontSizePx * 0.5; |
| | | } |
| | | |
| | | /** |
| | | * 智能计算字段的最大宽度 |
| | | * 根据右边是否有其他字段来动态计算maxWidth |
| | | * @param $left |
| | | * @param $top |
| | | * @param $backdropWidth |
| | | * @return float |
| | | */ |
| | | private function calculateFieldMaxWidth($left, $top, $backdropWidth = 375) |
| | | { |
| | | $rightFields = []; |
| | | $tolerance = 20; // 容差:判断是否在同一行的像素范围 |
| | | |
| | | // 收集所有可能的字段 |
| | | $fields = []; |
| | | |
| | | // 公司 |
| | | if (!empty($this->config['unit'][0])) { |
| | | $fields[] = [ |
| | | 'name' => 'unit', |
| | | 'left' => $this->config['unit'][0]['left'], |
| | | 'top' => $this->config['unit'][0]['top'] |
| | | ]; |
| | | } |
| | | |
| | | // 职位 |
| | | if (!empty($this->config['duties'][0])) { |
| | | $fields[] = [ |
| | | 'name' => 'duties', |
| | | 'left' => $this->config['duties'][0]['left'], |
| | | 'top' => $this->config['duties'][0]['top'] |
| | | ]; |
| | | } |
| | | |
| | | // 姓名 |
| | | if (!empty($this->config['name'])) { |
| | | $fields[] = [ |
| | | 'name' => 'name', |
| | | 'left' => $this->config['name']['left'], |
| | | 'top' => $this->config['name']['top'] |
| | | ]; |
| | | } |
| | | |
| | | // 地址 |
| | | if (!empty($this->config['address'][0])) { |
| | | $fields[] = [ |
| | | 'name' => 'address', |
| | | 'left' => $this->config['address'][0]['left'], |
| | | 'top' => $this->config['address'][0]['top'] |
| | | ]; |
| | | } |
| | | |
| | | // 邮箱 |
| | | if (!empty($this->config['mailbox'])) { |
| | | $fields[] = [ |
| | | 'name' => 'mailbox', |
| | | 'left' => $this->config['mailbox']['left'], |
| | | 'top' => $this->config['mailbox']['top'] |
| | | ]; |
| | | } |
| | | |
| | | // 手机 |
| | | if (!empty($this->config['mobile'])) { |
| | | $fields[] = [ |
| | | 'name' => 'mobile', |
| | | 'left' => $this->config['mobile']['left'], |
| | | 'top' => $this->config['mobile']['top'] |
| | | ]; |
| | | } |
| | | |
| | | // 微信 |
| | | if (!empty($this->config['wechat'])) { |
| | | $fields[] = [ |
| | | 'name' => 'wechat', |
| | | 'left' => $this->config['wechat']['left'], |
| | | 'top' => $this->config['wechat']['top'] |
| | | ]; |
| | | } |
| | | |
| | | // 电话 |
| | | if (!empty($this->config['phone'])) { |
| | | $fields[] = [ |
| | | 'name' => 'phone', |
| | | 'left' => $this->config['phone']['left'], |
| | | 'top' => $this->config['phone']['top'] |
| | | ]; |
| | | } |
| | | |
| | | // 找出右边最近的字段 |
| | | // 判断标准: left > 当前left, 且top在合理范围内(±20px) |
| | | foreach ($fields as $field) { |
| | | if ($field['left'] > $left && abs($field['top'] - $top) <= $tolerance) { |
| | | $rightFields[] = $field; |
| | | } |
| | | } |
| | | |
| | | // 如果右边有字段,取最近的一个的左边距 |
| | | if (count($rightFields) > 0) { |
| | | // 按left排序 |
| | | usort($rightFields, function($a, $b) { |
| | | return $a['left'] - $b['left']; |
| | | }); |
| | | return max($rightFields[0]['left'] - $left - 10, 50); |
| | | } |
| | | |
| | | // 如果右边没有字段,使用名片边缘 |
| | | return max($backdropWidth - $left - 10, 100); |
| | | } |
| | | |
| | | /** |
| | | * 写入支持换行的文本 |
| | | * @param $editor |
| | | * @param $text |
| | | * @param $x |
| | | * @param $y |
| | | * @param $fontSize |
| | | * @param $color |
| | | * @param $fontPath |
| | | * @param $maxWidth |
| | | * @return int 返回实际使用的行数 |
| | | */ |
| | | private function writeTextWithWrap($editor, $text, $x, $y, $fontSize, $color, $fontPath, $maxWidth) |
| | | { |
| | | $fontSizePt = $fontSize * 0.75; // px转pt |
| | | $lineHeight = $this->getLineHeight($fontSize); // 获取精确的行高 |
| | | $lines = []; |
| | | $currentLine = ''; |
| | | |
| | | // 按字符分割文本 |
| | | $chars = preg_split('//u', $text, -1, PREG_SPLIT_NO_EMPTY); |
| | | |
| | | foreach ($chars as $char) { |
| | | $testLine = $currentLine . $char; |
| | | $bbox = imagettfbbox($fontSizePt, 0, $fontPath, $testLine); |
| | | $lineWidth = $bbox[2] - $bbox[0]; |
| | | |
| | | // 使用maxWidth减去一点边距,确保不超出边界 |
| | | if ($lineWidth > $maxWidth - 2 && $currentLine !== '') { |
| | | $lines[] = $currentLine; |
| | | $currentLine = $char; |
| | | } else { |
| | | $currentLine = $testLine; |
| | | } |
| | | } |
| | | |
| | | if ($currentLine !== '') { |
| | | $lines[] = $currentLine; |
| | | } |
| | | |
| | | // 写入每一行 |
| | | foreach ($lines as $index => $line) { |
| | | // 第一行使用原始y坐标,后续行加上行高偏移 |
| | | $lineY = $y + ($index * $lineHeight); |
| | | imagettftext($editor, $fontSizePt, 0, $x, $lineY, $color, $fontPath, $line); |
| | | } |
| | | |
| | | // 返回实际占用的行数 |
| | | return count($lines); |
| | | } |
| | | |
| | | } |