Index.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. <?php
  2. namespace app\api\controller;
  3. use app\common\controller\Api;
  4. use app\service\AIGatewayService;
  5. use think\Db;
  6. use think\Exception;
  7. /**
  8. * 首页接口
  9. */
  10. class Index extends Api
  11. {
  12. protected $noNeedLogin = ['*'];
  13. protected $noNeedRight = ['*'];
  14. /**
  15. * 首页
  16. */
  17. public function index()
  18. {
  19. $this->success('请求成功');
  20. }
  21. /**
  22. * 图生文接口
  23. */
  24. public function img_to_txt() {
  25. $params = $this->request->param();
  26. $prompt = trim($params['prompt']);
  27. $old_path = trim($params['path']);
  28. $model = trim($params['model']);
  29. $status_val = trim($params['status_val']);
  30. // 参数验证
  31. if (empty($prompt) || empty($old_path) || empty($model)) {
  32. return json([
  33. 'code' => 1,
  34. 'msg' => '缺少必要参数',
  35. 'data' => null
  36. ]);
  37. }
  38. $aiGateway = new AIGatewayService();
  39. // 获取图片的base64数据和MIME类型
  40. $imageData = AIGatewayService::file_get_contents($old_path);
  41. $base64Data = $imageData['base64Data'];
  42. $mimeType = $imageData['mimeType'];
  43. $formattedPrompt = "1. 输出语言:所有内容必须为纯简体中文,禁止出现任何英文、拼音、数字、注释、解释性文字、引导语、示例、标点外的特殊符号;
  44. 2. 第一步(提取原图产品):
  45. - 用1句完整中文描述原图产品,字数控制在50字以内;
  46. - 必须包含「主体细节、产品名称、商标、类型、颜色、风格」核心;
  47. 3. 第二步(生成新图提示词):
  48. - 仅替换【模板提示词】中「产品主体」相关内容为第一步的产品描述;
  49. - 严格保留模板中「设计风格、光影、背景、比例、排版」等非产品相关信息;
  50. - 完全排除模板中的标题类信息,仅保留提示词核心内容;
  51. - 替换后必须完全保留原图产品的核心特征,禁止修改模板非产品核心信息;
  52. 4. 输出格式:不允许添加任何解释、引导、说明、示例、备注,仅返回「产品描述 + 替换后提示词」,直接输出纯文本;
  53. 【模板提示词】{$prompt}";
  54. $result = $aiGateway->callGptApi($model, $formattedPrompt, $mimeType, $base64Data);
  55. // Gemini模型响应格式处理
  56. $imgToTxtContent = $result['candidates'][0]['content']['parts'][0]['text'];
  57. // // 图生文成功后,调用文生文功能(gemini-2.0-flash)
  58. $txtToTxtPrompt = "转换成中文格式,去掉其他特殊符号,不允许添加任何解释、引导、说明、示例等文字:\n\n{$imgToTxtContent}";
  59. $txtToTxtResult = $aiGateway->txtGptApi($txtToTxtPrompt, 'gemini-2.0-flash');
  60. $finalContent = $txtToTxtResult['candidates'][0]['content']['parts'][0]['text'];
  61. // 处理调试输出内容
  62. $debugContent = json_encode($finalContent, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
  63. // 去掉特殊符号(只保留字母、数字、中文、常用标点和换行)
  64. $debugContent = preg_replace('/[^\p{Han}\w\s\n\r\.,,。!!??\-\::;;]/u', '', $debugContent);
  65. // 去掉第一个冒号前的文字
  66. $debugContent = preg_replace('/^[^:]+:/', '', $debugContent);
  67. return json([
  68. 'code' => 0,
  69. 'msg' => '处理成功',
  70. 'data' => [
  71. 'content' => $debugContent,
  72. 'english_content' => $imgToTxtContent
  73. ]
  74. ]);
  75. }
  76. /**
  77. * 文生图接口
  78. */
  79. public function txt_to_img() {
  80. $params = $this->request->param();
  81. $prompt = trim($params['prompt']);
  82. $model = trim($params['model']);
  83. $status_val = trim($params['status_val']);
  84. $size = trim($params['size']);
  85. // 获取产品信息
  86. $product = Db::name('product')->where('id', 1)->find();
  87. if (empty($product)) {
  88. return json([
  89. 'code' => 1,
  90. 'msg' => '产品不存在',
  91. 'data' => null
  92. ]);
  93. }
  94. $product_code = $product['product_code'];
  95. $product_code_prefix = substr($product_code, 0, 9); // 前九位
  96. // 构建URL路径(使用正斜杠)
  97. $url_path = '/uploads/merchant/' . $product_code_prefix . '/' . $product_code . '/newimg/';
  98. // 构建物理路径(使用正斜杠确保统一格式)
  99. $save_path = ROOT_PATH . 'public' . '/' . 'uploads' . '/' . 'merchant' . '/' . $product_code_prefix . '/' . $product_code . '/' . 'newimg' . '/';
  100. // 移除ROOT_PATH中可能存在的反斜杠,确保统一使用正斜杠
  101. $save_path = str_replace('\\', '/', $save_path);
  102. // 自动创建文件夹(如果不存在)
  103. if (!is_dir($save_path)) {
  104. mkdir($save_path, 0755, true);
  105. }
  106. // 调用AI生成图片
  107. $aiGateway = new AIGatewayService();
  108. $res = $aiGateway->callDalleApi($prompt, $model, $size);
  109. // 提取base64图片数据
  110. if (isset($res['candidates'][0]['content']['parts'][0]['text'])) {
  111. $text_content = $res['candidates'][0]['content']['parts'][0]['text'];
  112. // 匹配base64图片数据
  113. preg_match('/data:image\/(png|jpg|jpeg);base64,([^"]+)/', $text_content, $matches);
  114. if (empty($matches)) {
  115. return json([
  116. 'code' => 1,
  117. 'msg' => '未找到图片数据',
  118. 'data' => null
  119. ]);
  120. }
  121. $image_type = $matches[1];
  122. $base64_data = $matches[2];
  123. // 解码base64数据
  124. $image_data = base64_decode($base64_data);
  125. if ($image_data === false) {
  126. return json([
  127. 'code' => 1,
  128. 'msg' => '图片解码失败',
  129. 'data' => null
  130. ]);
  131. }
  132. // 生成唯一文件名(包含扩展名)
  133. $file_name = uniqid() . '.' . $image_type;
  134. $full_file_path = $save_path . $file_name;
  135. // 保存图片到文件系统
  136. if (!file_put_contents($full_file_path, $image_data)) {
  137. return json([
  138. 'code' => 1,
  139. 'msg' => '图片保存失败',
  140. 'data' => null
  141. ]);
  142. }
  143. // 生成数据库存储路径(使用正斜杠格式)
  144. $db_img_path = $url_path . $file_name;
  145. Db::name('product')->where('id', 1)->update(['product_new_img' => $db_img_path]);
  146. return json([
  147. 'code' => 0,
  148. 'msg' => '文生图请求成功并保存图片',
  149. 'data' => [
  150. 'img' => $db_img_path,
  151. 'product_id' => 1
  152. ]
  153. ]);
  154. } else {
  155. return json([
  156. 'code' => 1,
  157. 'msg' => 'AI返回格式错误',
  158. 'data' => null
  159. ]);
  160. }
  161. }
  162. /**
  163. * 图生图本地测试
  164. */
  165. public function imgtowimg()
  166. {
  167. $prompt = $this->request->param('prompt', '');
  168. $imgRelPath = 'uploads/operate/ai/Preview/arr/0835006071623.png';
  169. $imgPath = ROOT_PATH . 'public/' . $imgRelPath;
  170. if (!file_exists($imgPath)) {
  171. return json(['code' => 1, 'msg' => '原图不存在:' . $imgRelPath]);
  172. }
  173. $imgData = file_get_contents($imgPath);
  174. $base64Img = 'data:image/png;base64,' . base64_encode($imgData);
  175. $params = [
  176. 'prompt' => $prompt,
  177. 'negative_prompt' => '(deformed, distorted, disfigured:1.3), poorly drawn, bad anatomy',
  178. 'steps' => 20,
  179. 'sampler_name' => 'DPM++ 2M SDE',
  180. 'cfg_scale' => 7,
  181. 'seed' => -1,
  182. 'width' => 1024,
  183. 'height' => 1303,
  184. 'override_settings' => [
  185. 'sd_model_checkpoint' => 'realisticVisionV51_v51VAE-inpainting',
  186. 'sd_vae' => 'vae-ft-mse-840000-ema-pruned',
  187. 'CLIP_stop_at_last_layers' => 2
  188. ],
  189. 'clip_skip' => 2,
  190. 'alwayson_scripts' => [
  191. 'controlnet' => [
  192. 'args' => [[
  193. 'enabled' => true,
  194. 'input_image' => $base64Img,
  195. 'module' => 'inpaint_only+lama',
  196. 'model' => 'control_v11p_sd15_inpaint_fp16 [be8bc0ed]',
  197. 'weight' => 1,
  198. 'resize_mode' => 'Resize and Fill',
  199. 'pixel_perfect' => false,
  200. 'control_mode' => 'ControlNet is more important',
  201. 'starting_control_step' => 0,
  202. 'ending_control_step' => 1
  203. ]]
  204. ]
  205. ]
  206. ];
  207. $apiUrl = "http://20.0.17.188:45001/sdapi/v1/txt2img";
  208. $headers = ['Content-Type: application/json'];
  209. $ch = curl_init();
  210. curl_setopt($ch, CURLOPT_URL, $apiUrl);
  211. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  212. curl_setopt($ch, CURLOPT_POST, true);
  213. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  214. curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params, JSON_UNESCAPED_UNICODE));
  215. curl_setopt($ch, CURLOPT_TIMEOUT, 180);
  216. $response = curl_exec($ch);
  217. $error = curl_error($ch);
  218. curl_close($ch);
  219. if ($error) {
  220. return json(['code' => 1, 'msg' => '请求失败:' . $error]);
  221. }
  222. $data = json_decode($response, true);
  223. if (!isset($data['images'][0])) {
  224. return json(['code' => 1, 'msg' => '接口未返回图像数据']);
  225. }
  226. $resultImg = base64_decode($data['images'][0]);
  227. $saveDir = ROOT_PATH . 'public/uploads/img2img/';
  228. if (!is_dir($saveDir)) {
  229. mkdir($saveDir, 0755, true);
  230. }
  231. $originalBaseName = pathinfo($imgRelPath, PATHINFO_FILENAME);
  232. $fileName = $originalBaseName . '-' . time() . '-1024x1248.png';
  233. $savePath = $saveDir . $fileName;
  234. file_put_contents($savePath, $resultImg);
  235. return json([
  236. 'code' => 0,
  237. 'msg' => '图像生成成功',
  238. 'data' => [
  239. 'origin_url' => '/uploads/img2img/' . $fileName
  240. ]
  241. ]);
  242. }
  243. /**
  244. * 后期图像处理-单张图片高清放大处理
  245. */
  246. public function extra_image()
  247. {
  248. // 配置参数
  249. $config = [
  250. 'input_dir' => 'uploads/img2img/',
  251. 'output_dir' => 'uploads/extra_image/',
  252. 'api_url' => 'http://20.0.17.188:45001/sdapi/v1/extra-single-image',
  253. 'timeout' => 120, // 增加超时时间,高清处理可能耗时较长
  254. 'upscale_params' => [
  255. 'resize_mode' => 0,
  256. 'show_extras_results' => true,
  257. 'gfpgan_visibility' => 0, // 人脸修复关闭
  258. 'codeformer_visibility' => 0, // 人脸修复关闭
  259. 'codeformer_weight' => 0,
  260. 'upscaling_resize' => 2.45, // 放大倍数
  261. 'upscaling_crop' => true,
  262. 'upscaler_1' => 'R-ESRGAN 4x+ Anime6B', // 主放大模型
  263. 'upscaler_2' => 'None', // 不使用第二放大器
  264. 'extras_upscaler_2_visibility' => 0,
  265. 'upscale_first' => false,
  266. ]
  267. ];
  268. // 输入文件处理
  269. $imgRelPath = '0835006071623-1757406184-1024x1248.png';
  270. $imgPath = ROOT_PATH . 'public/' . $config['input_dir'] . $imgRelPath;
  271. if (!file_exists($imgPath)) {
  272. return json(['code' => 1, 'msg' => '原图不存在:' . $imgRelPath]);
  273. }
  274. // 读取并编码图片
  275. try {
  276. $imgData = file_get_contents($imgPath);
  277. if ($imgData === false) {
  278. throw new Exception('无法读取图片文件');
  279. }
  280. $base64Img = base64_encode($imgData);
  281. } catch (Exception $e) {
  282. return json(['code' => 1, 'msg' => '图片处理失败:' . $e->getMessage()]);
  283. }
  284. // 准备API请求数据
  285. $postData = array_merge($config['upscale_params'], ['image' => $base64Img]);
  286. $jsonData = json_encode($postData);
  287. if ($jsonData === false) {
  288. return json(['code' => 1, 'msg' => 'JSON编码失败']);
  289. }
  290. // 调用API进行高清放大
  291. $ch = curl_init();
  292. curl_setopt_array($ch, [
  293. CURLOPT_URL => $config['api_url'],
  294. CURLOPT_RETURNTRANSFER => true,
  295. CURLOPT_POST => true,
  296. CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
  297. CURLOPT_POSTFIELDS => $jsonData,
  298. CURLOPT_TIMEOUT => $config['timeout'],
  299. CURLOPT_CONNECTTIMEOUT => 30,
  300. ]);
  301. $response = curl_exec($ch);
  302. $error = curl_error($ch);
  303. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  304. curl_close($ch);
  305. if ($error) {
  306. return json(['code' => 1, 'msg' => 'API请求失败:' . $error]);
  307. }
  308. if ($httpCode !== 200) {
  309. return json(['code' => 1, 'msg' => 'API返回错误状态码:' . $httpCode]);
  310. }
  311. $data = json_decode($response, true);
  312. if (json_last_error() !== JSON_ERROR_NONE) {
  313. return json(['code' => 1, 'msg' => 'API返回数据解析失败']);
  314. }
  315. if (!isset($data['image']) || empty($data['image'])) {
  316. return json(['code' => 1, 'msg' => '接口未返回有效的图像数据']);
  317. }
  318. // 保存处理后的图片
  319. try {
  320. $resultImg = base64_decode($data['image']);
  321. if ($resultImg === false) {
  322. throw new Exception('Base64解码失败');
  323. }
  324. $saveDir = ROOT_PATH . 'public/' . $config['output_dir'];
  325. if (!is_dir($saveDir) && !mkdir($saveDir, 0755, true)) {
  326. throw new Exception('无法创建输出目录');
  327. }
  328. $originalBaseName = pathinfo($imgRelPath, PATHINFO_FILENAME);
  329. $fileName = $originalBaseName . '-hd.png'; // 使用-hd后缀更明确
  330. $savePath = $saveDir . $fileName;
  331. if (file_put_contents($savePath, $resultImg) === false) {
  332. throw new Exception('无法保存处理后的图片');
  333. }
  334. // 返回成功响应
  335. return json([
  336. 'code' => 0,
  337. 'msg' => '图像高清放大处理成功',
  338. 'data' => [
  339. 'url' => '/' . $config['output_dir'] . $fileName,
  340. 'original_size' => filesize($imgPath),
  341. 'processed_size' => filesize($savePath),
  342. 'resolution' => getimagesize($savePath), // 返回新图片的分辨率
  343. ]
  344. ]);
  345. } catch (Exception $e) {
  346. return json(['code' => 1, 'msg' => '保存结果失败:' . $e->getMessage()]);
  347. }
  348. }
  349. }