i
+
+
diff --git a/src/main/java/com/rj/utils/AliyunImageConversionUtil.java b/src/main/java/com/rj/utils/AliyunImageConversionUtil.java
new file mode 100644
index 0000000..9cfa5a8
--- /dev/null
+++ b/src/main/java/com/rj/utils/AliyunImageConversionUtil.java
@@ -0,0 +1,1523 @@
+package com.rj.utils;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.rj.service.MinIOService;
+import lombok.extern.slf4j.Slf4j;
+
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.*;
+import org.springframework.web.client.RestTemplate;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.*;
+import java.util.List;
+
+/**
+ * 阿里云背景生成API测试类
+ *
+ * 测试阿里云背景生成服务的API调用功能
+ *
+ * @author 系统生成
+ * @version 1.0
+ * @date 2025/1/30
+ */
+@Slf4j
+@SpringBootTest
+public class AliyunImageConversionUtil {
+
+ @Autowired
+ private RestTemplate restTemplate;
+
+ @Autowired
+ private MinIOService minIOService;
+
+ @Autowired
+ private ImageConversionUtil imageConversionUtil;
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+
+
+ /**
+ * 构建请求体
+ *
+ * @return 请求体Map
+ */
+ private Map buildRequestBody() {
+ Map requestBody = new HashMap<>();
+
+ // 设置模型
+ requestBody.put("model", "wanx-background-generation-v2");
+
+ // 构建input参数
+ Map input = new HashMap<>();
+ // 阿里云的原始图片,可以生成,是正确的
+// input.put("base_image_url", "https://vision-poster.oss-cn-shanghai.aliyuncs.com/lllcho.lc/data/test_data/images/main_images/new_main_img/a.png");
+// input.put("base_image_url", "http://101.35.52.237:19005/car/1760605862400_e3a319ba13184fdd9f4a08cdc3e3b30e.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20251016%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251016T091103Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=a2b4ae679552b6cfebcfeee228b84497a6eb458dc061dbf244f533fed41356b0");
+ // 转换后的 图片
+ input.put("base_image_url", "http://101.35.52.237:19005/car/1760607939127_e5af1be2272f4e5eaab6970d1dddb8e0.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20251016%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251016T094539Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=891cc2b70bfed8425a43edb2f742bbd95e19cce2ff16ed7e6171fc6e312ed7c8");
+ input.put("ref_image_url", "http://vision-poster.oss-cn-shanghai.aliyuncs.com/lllcho.lc/data/test_data/images/ref_images/c5e50d27be534709817b2ab080b0162f_0.jpg");
+ input.put("ref_prompt", "山脉和晚霞");
+
+ // 构建reference_edge参数
+ Map referenceEdge = new HashMap<>();
+
+ // 前景边缘
+ List foregroundEdge = Arrays.asList(
+ "https://vision-poster.oss-cn-shanghai.aliyuncs.com/lllcho.lc/data/test_data/images/huaban_soft_edge/6cdd13941cef1b11d885aea1717b983ae566b8efc9094-vcsvxa_fw658webp.png",
+ "http://vision-poster.oss-cn-shanghai.aliyuncs.com/lllcho.lc/data/test_data/images/ref_edge/2c36cc4b7da027279e87311dac48fc2d5d784b1e72c0e-x4f1wC_fw658webp.png"
+ );
+ referenceEdge.put("foreground_edge", foregroundEdge);
+
+ // 背景边缘
+ List backgroundEdge = Arrays.asList(
+ "http://vision-poster.oss-cn-shanghai.aliyuncs.com/lllcho.lc/data/test_data/images/ref_edge/0718a9741e07c52ca5506e75c4f2b99e22fff68a4c7d3-P9WGLr_fw658webp.png"
+ );
+ referenceEdge.put("background_edge", backgroundEdge);
+
+ // 前景边缘提示词
+ List foregroundEdgePrompt = Arrays.asList(
+ "粉色桃花",
+ "可爱小狗"
+ );
+ referenceEdge.put("foreground_edge_prompt", foregroundEdgePrompt);
+
+ // 背景边缘提示词
+ List backgroundEdgePrompt = Arrays.asList(
+ "树叶"
+ );
+ referenceEdge.put("background_edge_prompt", backgroundEdgePrompt);
+
+ // input.put("reference_edge", referenceEdge);
+ requestBody.put("input", input);
+
+ // 构建parameters参数
+ Map parameters = new HashMap<>();
+ parameters.put("n", 4);
+ parameters.put("ref_prompt_weight", 0.5);
+ parameters.put("model_version", "v3");
+ requestBody.put("parameters", parameters);
+
+ return requestBody;
+ }
+
+
+
+ /**
+ * 生成唯一文件名(测试用)
+ *
+ * @param extension 文件扩展名
+ * @return 唯一文件名
+ */
+ private String generateUniqueFileNameForTest(String extension) {
+ long timestamp = System.currentTimeMillis();
+ String uuid = UUID.randomUUID().toString().replace("-", "");
+ return timestamp + "_" + uuid + "." + extension;
+ }
+
+ /**
+ * 完整的图片转换和阿里云API调用流程
+ * #########################################################################################################################################################
+ */
+ @Test
+ public void testImageAnalysisAndConversion() {
+ try {
+ log.info("开始完整的图片转换和阿里云API调用流程");
+
+ // 1. 分析阿里云图片特征(参考)
+ String aliyunImageUrl = "https://vision-poster.oss-cn-shanghai.aliyuncs.com/lllcho.lc/data/test_data/images/main_images/new_main_img/a.png";
+ analyzeImageCharacteristics(aliyunImageUrl, "阿里云参考图片");
+
+ // 2. 分析您的原始图片特征
+ String yourImageUrl = "http://101.35.52.237:19005/car/1760605862400_e3a319ba13184fdd9f4a08cdc3e3b30e.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20251016%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251016T091103Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=a2b4ae679552b6cfebcfeee228b84497a6eb458dc061dbf244f533fed41356b0";
+ analyzeImageCharacteristics(yourImageUrl, "您的原始图片");
+
+ // 3. 转换图片为符合阿里云要求的格式(使用工具类)
+ byte[] convertedImageBytes = imageConversionUtil.convertImageToAliyunFormatAdvanced(yourImageUrl);
+ if (convertedImageBytes == null) {
+ log.error("图片转换失败,无法继续");
+ return;
+ }
+ log.info("图片转换完成,转换后大小: {} bytes", convertedImageBytes.length);
+
+ // 4. 上传转换后的图片到MinIO
+ String minioFileName = uploadImageToMinIO(convertedImageBytes);
+ if (minioFileName == null) {
+ log.error("上传到MinIO失败,无法继续");
+ return;
+ }
+ log.info("图片上传到MinIO成功,文件名: {}", minioFileName);
+
+ // 4.1 同时保存一份到本地电脑
+ String localFilePath = saveImageToLocal(convertedImageBytes, minioFileName);
+ if (localFilePath != null) {
+ log.info("图片已保存到本地: {}", localFilePath);
+ } else {
+ log.warn("保存到本地失败,但MinIO上传成功,继续执行");
+ }
+
+ // 5. 生成临时访问链接
+ String tempUrl = generateTempUrl(minioFileName);
+ if (tempUrl == null) {
+ log.error("生成临时访问链接失败,无法继续");
+ return;
+ }
+ log.info("生成临时访问链接: {}", tempUrl);
+
+ // 6. 验证转换后的图片特征
+ analyzeImageCharacteristics(tempUrl, "转换后的图片");
+
+ // 7. 使用转换后的图片调用阿里云API
+ callAliyunAPIWithConvertedImage(tempUrl);
+
+ } catch (Exception e) {
+ log.error("完整的图片转换和API调用流程失败", e);
+ }
+ }
+
+ /**
+ * 分析图片特征
+ *
+ * @param imageUrl 图片URL
+ * @param imageName 图片名称
+ */
+ private void analyzeImageCharacteristics(String imageUrl, String imageName) {
+ try {
+ log.info("=== 分析{}特征 ===", imageName);
+
+ // 下载图片
+ BufferedImage image = downloadImage(imageUrl);
+ if (image == null) {
+ log.error("无法下载图片: {}", imageUrl);
+ return;
+ }
+
+ // 分析基本特征
+ int width = image.getWidth();
+ int height = image.getHeight();
+ int type = image.getType();
+ int maxDimension = Math.max(width, height);
+
+ log.info("图片尺寸: {}x{}", width, height);
+ log.info("图片类型: {} ({})", type, getImageTypeName(type));
+ log.info("最大尺寸: {} (是否超过2048: {})", maxDimension, maxDimension > 2048);
+
+ // 分析颜色模式
+ analyzeColorMode(image);
+
+ // 分析透明度
+ analyzeTransparency(image);
+
+ // 分析文件格式
+ analyzeFileFormat(imageUrl);
+
+ } catch (Exception e) {
+ log.error("分析图片特征失败: {}", imageUrl, e);
+ }
+ }
+
+ /**
+ * 下载图片
+ *
+ * @param imageUrl 图片URL
+ * @return BufferedImage对象
+ */
+ private BufferedImage downloadImage(String imageUrl) {
+ try {
+ URL url = new URL(imageUrl);
+ return ImageIO.read(url);
+ } catch (Exception e) {
+ log.error("下载图片失败: {}", imageUrl, e);
+ return null;
+ }
+ }
+
+ /**
+ * 获取图片类型名称
+ *
+ * @param type 图片类型
+ * @return 类型名称
+ */
+ private String getImageTypeName(int type) {
+ switch (type) {
+ case BufferedImage.TYPE_INT_RGB:
+ return "RGB";
+ case BufferedImage.TYPE_INT_ARGB:
+ return "ARGB";
+ case BufferedImage.TYPE_INT_ARGB_PRE:
+ return "ARGB_PRE";
+ case BufferedImage.TYPE_4BYTE_ABGR:
+ return "ABGR";
+ case BufferedImage.TYPE_4BYTE_ABGR_PRE:
+ return "ABGR_PRE";
+ case BufferedImage.TYPE_3BYTE_BGR:
+ return "BGR";
+ case BufferedImage.TYPE_BYTE_GRAY:
+ return "GRAY";
+ case BufferedImage.TYPE_USHORT_555_RGB:
+ return "RGB_555";
+ case BufferedImage.TYPE_USHORT_565_RGB:
+ return "RGB_565";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * 分析颜色模式
+ *
+ * @param image 图片对象
+ */
+ private void analyzeColorMode(BufferedImage image) {
+ try {
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ // 采样分析颜色
+ int sampleSize = Math.min(100, Math.min(width, height));
+ int stepX = width / sampleSize;
+ int stepY = height / sampleSize;
+
+ int transparentPixels = 0;
+ int opaquePixels = 0;
+ int totalPixels = 0;
+
+ for (int y = 0; y < height; y += stepY) {
+ for (int x = 0; x < width; x += stepX) {
+ int rgb = image.getRGB(x, y);
+ int alpha = (rgb >> 24) & 0xFF;
+
+ if (alpha == 0) {
+ transparentPixels++;
+ } else if (alpha == 255) {
+ opaquePixels++;
+ }
+ totalPixels++;
+ }
+ }
+
+ double transparentRatio = (double) transparentPixels / totalPixels;
+ double opaqueRatio = (double) opaquePixels / totalPixels;
+
+ log.info("透明度分析:");
+ log.info(" 透明像素比例: {:.2f}%", transparentRatio * 100);
+ log.info(" 不透明像素比例: {:.2f}%", opaqueRatio * 100);
+ log.info(" 半透明像素比例: {:.2f}%", (1 - transparentRatio - opaqueRatio) * 100);
+
+ } catch (Exception e) {
+ log.error("分析颜色模式失败", e);
+ }
+ }
+
+ /**
+ * 分析透明度
+ *
+ * @param image 图片对象
+ */
+ private void analyzeTransparency(BufferedImage image) {
+ try {
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ boolean hasTransparency = image.getColorModel().hasAlpha();
+ log.info("是否支持透明度: {}", hasTransparency);
+
+ // 检查是否有透明像素
+ boolean hasTransparentPixels = false;
+ for (int y = 0; y < height; y += 10) { // 采样检查
+ for (int x = 0; x < width; x += 10) {
+ int rgb = image.getRGB(x, y);
+ int alpha = (rgb >> 24) & 0xFF;
+ if (alpha < 255) {
+ hasTransparentPixels = true;
+ break;
+ }
+ }
+ if (hasTransparentPixels) break;
+ }
+
+ log.info("是否包含透明像素: {}", hasTransparentPixels);
+
+ } catch (Exception e) {
+ log.error("分析透明度失败", e);
+ }
+ }
+
+ /**
+ * 分析文件格式
+ *
+ * @param imageUrl 图片URL
+ */
+ private void analyzeFileFormat(String imageUrl) {
+ try {
+ if (imageUrl.startsWith("file://")) {
+ String filePath = imageUrl.substring(7);
+ File file = new File(filePath);
+ if (file.exists()) {
+ log.info("文件大小: {} bytes", file.length());
+ log.info("文件扩展名: {}", getFileExtension(file.getName()));
+ }
+ } else {
+ URL url = new URL(imageUrl);
+ try (InputStream inputStream = url.openStream()) {
+ byte[] header = new byte[8];
+ inputStream.read(header);
+
+ log.info("文件头: {}", bytesToHex(header));
+
+ // 检查PNG格式
+ if (header[0] == (byte) 0x89 && header[1] == 0x50 && header[2] == 0x4E && header[3] == 0x47) {
+ log.info("文件格式: PNG");
+ } else if (header[0] == (byte) 0xFF && header[1] == (byte) 0xD8) {
+ log.info("文件格式: JPEG");
+ } else {
+ log.info("文件格式: 未知");
+ }
+ }
+ }
+ } catch (Exception e) {
+ log.error("分析文件格式失败", e);
+ }
+ }
+
+ /**
+ * 字节数组转十六进制字符串
+ *
+ * @param bytes 字节数组
+ * @return 十六进制字符串
+ */
+ private String bytesToHex(byte[] bytes) {
+ StringBuilder result = new StringBuilder();
+ for (byte b : bytes) {
+ result.append(String.format("%02X ", b));
+ }
+ return result.toString();
+ }
+
+ /**
+ * 获取文件扩展名
+ *
+ * @param filename 文件名
+ * @return 扩展名
+ */
+ private String getFileExtension(String filename) {
+ if (filename == null || filename.lastIndexOf('.') == -1) {
+ return "";
+ }
+ return filename.substring(filename.lastIndexOf('.') + 1);
+ }
+
+ /**
+ * 转换图片为符合阿里云要求的格式(返回字节数组)
+ * 阿里云要求:主体图像必须为带透明背景的RGBA四通道图像,PNG格式,长边不超过2048像素
+ *
+ * 注意:此方法为基础版本,推荐使用 convertImageToAliyunFormatAdvanced 方法获得更好的效果
+ *
+ * @param imageUrl 原始图片URL
+ * @return 转换后的图片字节数组
+ */
+ private byte[] convertImageToAliyunFormatBytes(String imageUrl) {
+ try {
+ log.info("开始转换图片为阿里云格式");
+
+ // 1. 下载原始图片
+ BufferedImage originalImage = downloadImage(imageUrl);
+ if (originalImage == null) {
+ log.error("无法下载原始图片");
+ return null;
+ }
+
+ int width = originalImage.getWidth();
+ int height = originalImage.getHeight();
+ log.info("原始图片尺寸: {}x{}", width, height);
+
+ // 2. 检查尺寸,如果超过2048则缩放
+ if (Math.max(width, height) > 2048) {
+ double scale = 2048.0 / Math.max(width, height);
+ int newWidth = (int) (width * scale);
+ int newHeight = (int) (height * scale);
+ log.info("图片尺寸过大,缩放至: {}x{}", newWidth, newHeight);
+
+ BufferedImage scaledImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2d = scaledImage.createGraphics();
+
+ // 设置高质量渲染
+ g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
+
+ // 绘制原始图片到缩放图片上
+ g2d.drawImage(originalImage, 0, 0, newWidth, newHeight, null);
+ g2d.dispose();
+
+ originalImage = scaledImage;
+ width = newWidth;
+ height = newHeight;
+ }
+
+ // 3. 创建真正的RGBA四通道图像,确保支持透明度
+ BufferedImage rgbaImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2d = rgbaImage.createGraphics();
+
+ // 设置高质量渲染
+ g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
+ g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
+
+ // 先清空背景为透明
+ g2d.setComposite(AlphaComposite.Clear);
+ g2d.fillRect(0, 0, width, height);
+
+ // 设置正常合成模式
+ g2d.setComposite(AlphaComposite.SrcOver);
+
+ // 绘制原始图片到RGBA图片上,保持透明度
+ g2d.drawImage(originalImage, 0, 0, null);
+ g2d.dispose();
+
+ // 4. 验证RGBA图像是否真正支持透明度
+ boolean hasTransparency = rgbaImage.getColorModel().hasAlpha();
+ log.info("RGBA图像是否支持透明度: {}", hasTransparency);
+
+ // 检查是否有透明像素
+ boolean hasTransparentPixels = false;
+ for (int y = 0; y < height; y += Math.max(1, height / 20)) {
+ for (int x = 0; x < width; x += Math.max(1, width / 20)) {
+ int rgb = rgbaImage.getRGB(x, y);
+ int alpha = (rgb >> 24) & 0xFF;
+ if (alpha < 255) {
+ hasTransparentPixels = true;
+ break;
+ }
+ }
+ if (hasTransparentPixels) break;
+ }
+ log.info("RGBA图像是否包含透明像素: {}", hasTransparentPixels);
+
+ // 5. 转换为PNG格式字节数组
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ boolean success = ImageIO.write(rgbaImage, "PNG", baos);
+ if (success) {
+ byte[] imageBytes = baos.toByteArray();
+ log.info("图片转换成功,大小: {} bytes", imageBytes.length);
+
+ // 验证PNG文件头
+ if (imageBytes.length >= 8) {
+ boolean isPng = (imageBytes[0] == (byte) 0x89 && imageBytes[1] == 0x50 &&
+ imageBytes[2] == 0x4E && imageBytes[3] == 0x47);
+ log.info("输出文件是否为PNG格式: {}", isPng);
+ }
+
+ return imageBytes;
+ } else {
+ log.error("图片转换失败");
+ return null;
+ }
+
+ } catch (Exception e) {
+ log.error("转换图片失败", e);
+ return null;
+ }
+ }
+
+ /**
+ * 高级图片转换方法,专门处理透明背景需求
+ * 确保生成符合阿里云要求的RGBA四通道PNG图像
+ *
+ * @param imageUrl 原始图片URL
+ * @return 转换后的图片字节数组
+ */
+ private byte[] convertImageToAliyunFormatAdvanced(String imageUrl) {
+ try {
+ log.info("开始高级图片转换为阿里云格式");
+
+ // 1. 下载原始图片
+ BufferedImage originalImage = downloadImage(imageUrl);
+ if (originalImage == null) {
+ log.error("无法下载原始图片");
+ return null;
+ }
+
+ int width = originalImage.getWidth();
+ int height = originalImage.getHeight();
+ log.info("原始图片尺寸: {}x{}, 类型: {}", width, height, getImageTypeName(originalImage.getType()));
+
+ // 2. 检查尺寸,如果超过2048则缩放
+ if (Math.max(width, height) > 2048) {
+ double scale = 2048.0 / Math.max(width, height);
+ int newWidth = (int) (width * scale);
+ int newHeight = (int) (height * scale);
+ log.info("图片尺寸过大,缩放至: {}x{}", newWidth, newHeight);
+
+ BufferedImage scaledImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2d = scaledImage.createGraphics();
+
+ // 设置最高质量渲染
+ g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
+ g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
+
+ // 绘制原始图片到缩放图片上
+ g2d.drawImage(originalImage, 0, 0, newWidth, newHeight, null);
+ g2d.dispose();
+
+ originalImage = scaledImage;
+ width = newWidth;
+ height = newHeight;
+ }
+
+ // 3. 创建真正的RGBA四通道图像,强制透明背景
+ BufferedImage rgbaImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2d = rgbaImage.createGraphics();
+
+ // 设置最高质量渲染
+ g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
+ g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
+
+ // 强制清空背景为完全透明
+ g2d.setComposite(AlphaComposite.Clear);
+ g2d.fillRect(0, 0, width, height);
+
+ // 设置正常合成模式
+ g2d.setComposite(AlphaComposite.SrcOver);
+
+ // 如果原始图片没有透明背景,使用智能背景移除
+ if (!originalImage.getColorModel().hasAlpha() || !hasTransparentPixels(originalImage)) {
+ log.info("原始图片没有透明背景,使用智能背景移除");
+
+ // 使用智能背景移除算法
+ BufferedImage transparentImage = createTransparentBackgroundImage(originalImage);
+
+ // 将处理后的图片绘制到RGBA图像上
+ g2d.drawImage(transparentImage, 0, 0, null);
+
+ // 验证透明像素数量
+ int transparentCount = 0;
+ for (int y = 0; y < height; y += Math.max(1, height / 50)) {
+ for (int x = 0; x < width; x += Math.max(1, width / 50)) {
+ int rgb = rgbaImage.getRGB(x, y);
+ int alpha = (rgb >> 24) & 0xFF;
+ if (alpha == 0) {
+ transparentCount++;
+ }
+ }
+ }
+
+ log.info("智能背景移除完成,采样透明像素比例: {:.2f}%",
+ (double) transparentCount / ((width / Math.max(1, width / 50)) * (height / Math.max(1, height / 50))) * 100);
+ } else {
+ // 原始图片已有透明度,直接绘制
+ g2d.drawImage(originalImage, 0, 0, null);
+ }
+
+ g2d.dispose();
+
+ // 4. 深度验证RGBA图像
+ boolean hasTransparency = rgbaImage.getColorModel().hasAlpha();
+ log.info("RGBA图像是否支持透明度: {}", hasTransparency);
+
+ // 详细分析透明度
+ analyzeTransparency(rgbaImage);
+ analyzeColorMode(rgbaImage);
+
+ // 5. 转换为PNG格式字节数组
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ boolean success = ImageIO.write(rgbaImage, "PNG", baos);
+ if (success) {
+ byte[] imageBytes = baos.toByteArray();
+ log.info("高级图片转换成功,大小: {} bytes", imageBytes.length);
+
+ // 验证PNG文件头
+ if (imageBytes.length >= 8) {
+ boolean isPng = (imageBytes[0] == (byte) 0x89 && imageBytes[1] == 0x50 &&
+ imageBytes[2] == 0x4E && imageBytes[3] == 0x47);
+ log.info("输出文件是否为PNG格式: {}", isPng);
+
+ // 检查PNG是否包含透明度信息
+ if (imageBytes.length > 25) {
+ // 检查IHDR chunk中的颜色类型
+ boolean hasAlphaChannel = false;
+ for (int i = 0; i < imageBytes.length - 25; i++) {
+ if (imageBytes[i] == 'I' && imageBytes[i+1] == 'H' &&
+ imageBytes[i+2] == 'D' && imageBytes[i+3] == 'R') {
+ // 颜色类型在第25个字节
+ int colorType = imageBytes[i + 25] & 0xFF;
+ hasAlphaChannel = (colorType == 4 || colorType == 6);
+ log.info("PNG颜色类型: {}, 是否包含Alpha通道: {}", colorType, hasAlphaChannel);
+ break;
+ }
+ }
+ }
+ }
+
+ return imageBytes;
+ } else {
+ log.error("高级图片转换失败");
+ return null;
+ }
+
+ } catch (Exception e) {
+ log.error("高级图片转换失败", e);
+ return null;
+ }
+ }
+
+ /**
+ * 上传图片到MinIO
+ *
+ * @param imageBytes 图片字节数组
+ * @return MinIO文件名
+ */
+ private String uploadImageToMinIO(byte[] imageBytes) {
+ try {
+ log.info("开始上传图片到MinIO");
+
+ // 生成唯一文件名
+ String fileName = generateUniqueFileNameForTest("png");
+
+ // 上传到MinIO
+ String materialUrl = minIOService.uploadFile(imageBytes, fileName, "image/png");
+ if (materialUrl != null) {
+ log.info("图片上传到MinIO成功: {}", materialUrl);
+ return fileName;
+ } else {
+ log.error("图片上传到MinIO失败");
+ return null;
+ }
+
+ } catch (Exception e) {
+ log.error("上传图片到MinIO失败", e);
+ return null;
+ }
+ }
+
+ /**
+ * 保存图片到本地电脑
+ *
+ * @param imageBytes 图片字节数组
+ * @param minioFileName MinIO文件名(用作本地文件名)
+ * @return 本地文件路径
+ */
+ private String saveImageToLocal(byte[] imageBytes, String minioFileName) {
+ try {
+ log.info("开始保存图片到本地电脑");
+
+ // 创建本地保存目录
+ String localDir = "converted_images";
+ File dir = new File(localDir);
+ if (!dir.exists()) {
+ boolean created = dir.mkdirs();
+ if (!created) {
+ log.error("创建本地目录失败: {}", localDir);
+ return null;
+ }
+ log.info("创建本地目录: {}", localDir);
+ }
+
+ // 构建本地文件路径
+ String localFileName = "converted_" + minioFileName;
+ String localFilePath = localDir + File.separator + localFileName;
+ File localFile = new File(localFilePath);
+
+ // 写入文件
+ try (FileOutputStream fos = new FileOutputStream(localFile)) {
+ fos.write(imageBytes);
+ fos.flush();
+ }
+
+ log.info("图片保存到本地成功: {}", localFile.getAbsolutePath());
+ log.info("本地文件大小: {} bytes", localFile.length());
+
+ return localFile.getAbsolutePath();
+
+ } catch (Exception e) {
+ log.error("保存图片到本地失败", e);
+ return null;
+ }
+ }
+
+ /**
+ * 生成临时访问链接
+ *
+ * @param fileName MinIO文件名
+ * @return 临时访问链接
+ */
+ private String generateTempUrl(String fileName) {
+ try {
+ log.info("开始生成临时访问链接,文件名: {}", fileName);
+
+ String tempUrl = minIOService.generateTempUrl(fileName);
+ if (tempUrl != null) {
+ log.info("临时访问链接生成成功: {}", tempUrl);
+ return tempUrl;
+ } else {
+ log.error("临时访问链接生成失败");
+ return null;
+ }
+
+ } catch (Exception e) {
+ log.error("生成临时访问链接失败", e);
+ return null;
+ }
+ }
+
+ /**
+ * 创建带透明背景的图片(专门处理无透明背景的图片)
+ * 使用高级背景移除算法,确保彻底移除背景
+ *
+ * @param originalImage 原始图片
+ * @return 带透明背景的图片
+ */
+ private BufferedImage createTransparentBackgroundImage(BufferedImage originalImage) {
+ int width = originalImage.getWidth();
+ int height = originalImage.getHeight();
+
+ log.info("开始高级背景移除处理,图片尺寸: {}x{}", width, height);
+
+ // 创建RGBA图像
+ BufferedImage transparentImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+
+ // 第一步:分析图片,找到主体区域
+ int[] edgePixels = findEdgePixels(originalImage);
+ int leftEdge = edgePixels[0];
+ int rightEdge = edgePixels[1];
+ int topEdge = edgePixels[2];
+ int bottomEdge = edgePixels[3];
+
+ log.info("检测到主体区域: 左={}, 右={}, 上={}, 下={}", leftEdge, rightEdge, topEdge, bottomEdge);
+
+ // 第二步:分析背景颜色特征
+ Color[] backgroundColors = analyzeBackgroundColors(originalImage, edgePixels);
+ log.info("分析到背景颜色数量: {}", backgroundColors.length);
+
+ // 第三步:高级背景移除处理
+ int transparentCount = 0;
+ int subjectCount = 0;
+
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int rgb = originalImage.getRGB(x, y);
+ int r = (rgb >> 16) & 0xFF;
+ int g = (rgb >> 8) & 0xFF;
+ int b = rgb & 0xFF;
+
+ // 多重判断是否为主体像素
+ boolean isSubject = isSubjectPixel(x, y, r, g, b, edgePixels, backgroundColors, originalImage);
+
+ if (isSubject) {
+ // 主体区域:保持原色,但可能进行边缘羽化
+ int alpha = calculateEdgeAlpha(x, y, edgePixels, width, height);
+ transparentImage.setRGB(x, y, (alpha << 24) | (rgb & 0xFFFFFF));
+ subjectCount++;
+ } else {
+ // 背景区域:设为透明
+ transparentImage.setRGB(x, y, 0x00000000);
+ transparentCount++;
+ }
+ }
+ }
+
+ log.info("背景移除完成 - 主体像素: {}, 透明像素: {}, 主体比例: {:.2f}%",
+ subjectCount, transparentCount, (double) subjectCount / (width * height) * 100);
+
+ // 第四步:后处理优化
+ BufferedImage optimizedImage = postProcessTransparentImage(transparentImage, originalImage);
+
+ return optimizedImage;
+ }
+
+ /**
+ * 分析背景颜色特征
+ *
+ * @param image 图片对象
+ * @param edgePixels 主体边缘坐标
+ * @return 背景颜色数组
+ */
+ private Color[] analyzeBackgroundColors(BufferedImage image, int[] edgePixels) {
+ int width = image.getWidth();
+ int height = image.getHeight();
+ int leftEdge = edgePixels[0];
+ int rightEdge = edgePixels[1];
+ int topEdge = edgePixels[2];
+ int bottomEdge = edgePixels[3];
+
+ List backgroundColors = new ArrayList<>();
+
+ // 采样边缘区域的背景颜色
+ int sampleSize = 100;
+ for (int i = 0; i < sampleSize; i++) {
+ int x, y;
+
+ // 随机选择边缘区域外的像素
+ if (i % 4 == 0) {
+ // 左侧边缘
+ x = (int) (Math.random() * Math.max(1, leftEdge));
+ y = (int) (Math.random() * height);
+ } else if (i % 4 == 1) {
+ // 右侧边缘
+ x = rightEdge + (int) (Math.random() * Math.max(1, width - rightEdge));
+ y = (int) (Math.random() * height);
+ } else if (i % 4 == 2) {
+ // 上侧边缘
+ x = (int) (Math.random() * width);
+ y = (int) (Math.random() * Math.max(1, topEdge));
+ } else {
+ // 下侧边缘
+ x = (int) (Math.random() * width);
+ y = bottomEdge + (int) (Math.random() * Math.max(1, height - bottomEdge));
+ }
+
+ if (x >= 0 && x < width && y >= 0 && y < height) {
+ int rgb = image.getRGB(x, y);
+ int r = (rgb >> 16) & 0xFF;
+ int g = (rgb >> 8) & 0xFF;
+ int b = rgb & 0xFF;
+ backgroundColors.add(new Color(r, g, b));
+ }
+ }
+
+ return backgroundColors.toArray(new Color[0]);
+ }
+
+ /**
+ * 判断像素是否为主体像素(多重条件判断)
+ *
+ * @param x 像素X坐标
+ * @param y 像素Y坐标
+ * @param r 红色值
+ * @param g 绿色值
+ * @param b 蓝色值
+ * @param edgePixels 主体边缘坐标
+ * @param backgroundColors 背景颜色数组
+ * @param image 原始图片
+ * @return 是否为主体像素
+ */
+ private boolean isSubjectPixel(int x, int y, int r, int g, int b, int[] edgePixels,
+ Color[] backgroundColors, BufferedImage image) {
+ int leftEdge = edgePixels[0];
+ int rightEdge = edgePixels[1];
+ int topEdge = edgePixels[2];
+ int bottomEdge = edgePixels[3];
+
+ // 条件1:位置判断 - 必须在主体区域内
+ boolean inSubjectArea = (x >= leftEdge && x <= rightEdge && y >= topEdge && y <= bottomEdge);
+ if (!inSubjectArea) {
+ return false;
+ }
+
+ // 条件2:颜色判断 - 不能是明显的背景色
+ boolean isBackgroundColor = false;
+ for (Color bgColor : backgroundColors) {
+ int colorDiff = Math.abs(r - bgColor.getRed()) +
+ Math.abs(g - bgColor.getGreen()) +
+ Math.abs(b - bgColor.getBlue());
+ if (colorDiff < 30) { // 颜色相似度阈值
+ isBackgroundColor = true;
+ break;
+ }
+ }
+
+ // 条件3:亮度判断 - 不能是过亮或过暗的背景色
+ int brightness = (r + g + b) / 3;
+ boolean isExtremeBrightness = brightness > 240 || brightness < 20;
+
+ // 条件4:颜色均匀性判断 - 不能是过于均匀的颜色
+ boolean isUniformColor = Math.abs(r - g) < 10 && Math.abs(g - b) < 10 && Math.abs(r - b) < 10;
+
+ // 条件5:边缘检测 - 检查周围像素的变化
+ boolean hasColorVariation = hasSignificantColorVariation(image, x, y);
+
+ // 综合判断:必须满足主体区域条件,且不满足背景特征
+ return inSubjectArea && !isBackgroundColor && !isExtremeBrightness &&
+ (!isUniformColor || hasColorVariation);
+ }
+
+ /**
+ * 检查像素周围是否有显著的颜色变化
+ *
+ * @param image 图片对象
+ * @param x 像素X坐标
+ * @param y 像素Y坐标
+ * @return 是否有显著颜色变化
+ */
+ private boolean hasSignificantColorVariation(BufferedImage image, int x, int y) {
+ int width = image.getWidth();
+ int height = image.getHeight();
+ int centerRgb = image.getRGB(x, y);
+ int centerR = (centerRgb >> 16) & 0xFF;
+ int centerG = (centerRgb >> 8) & 0xFF;
+ int centerB = centerRgb & 0xFF;
+
+ int variationCount = 0;
+ int totalSamples = 0;
+
+ // 检查周围8个像素
+ for (int dy = -1; dy <= 1; dy++) {
+ for (int dx = -1; dx <= 1; dx++) {
+ if (dx == 0 && dy == 0) continue;
+
+ int nx = x + dx;
+ int ny = y + dy;
+
+ if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
+ int neighborRgb = image.getRGB(nx, ny);
+ int neighborR = (neighborRgb >> 16) & 0xFF;
+ int neighborG = (neighborRgb >> 8) & 0xFF;
+ int neighborB = neighborRgb & 0xFF;
+
+ int colorDiff = Math.abs(centerR - neighborR) +
+ Math.abs(centerG - neighborG) +
+ Math.abs(centerB - neighborB);
+
+ if (colorDiff > 30) { // 颜色差异阈值
+ variationCount++;
+ }
+ totalSamples++;
+ }
+ }
+ }
+
+ return totalSamples > 0 && (double) variationCount / totalSamples > 0.3;
+ }
+
+ /**
+ * 计算边缘像素的Alpha值(羽化效果)
+ *
+ * @param x 像素X坐标
+ * @param y 像素Y坐标
+ * @param edgePixels 主体边缘坐标
+ * @param width 图片宽度
+ * @param height 图片高度
+ * @return Alpha值
+ */
+ private int calculateEdgeAlpha(int x, int y, int[] edgePixels, int width, int height) {
+ int leftEdge = edgePixels[0];
+ int rightEdge = edgePixels[1];
+ int topEdge = edgePixels[2];
+ int bottomEdge = edgePixels[3];
+
+ // 计算到边缘的距离
+ int distToLeft = x - leftEdge;
+ int distToRight = rightEdge - x;
+ int distToTop = y - topEdge;
+ int distToBottom = bottomEdge - y;
+
+ int minDist = Math.min(Math.min(distToLeft, distToRight), Math.min(distToTop, distToBottom));
+
+ // 如果距离边缘很近,进行羽化处理
+ if (minDist <= 3) {
+ return Math.max(128, 255 - (3 - minDist) * 40); // 边缘羽化
+ }
+
+ return 255; // 完全不透明
+ }
+
+ /**
+ * 后处理透明图片,进一步优化背景移除效果
+ *
+ * @param transparentImage 初步处理的透明图片
+ * @param originalImage 原始图片
+ * @return 优化后的透明图片
+ */
+ private BufferedImage postProcessTransparentImage(BufferedImage transparentImage, BufferedImage originalImage) {
+ int width = transparentImage.getWidth();
+ int height = transparentImage.getHeight();
+
+ log.info("开始后处理优化,进一步清理残留背景");
+
+ BufferedImage optimizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+
+ // 复制透明图片
+ Graphics2D g2d = optimizedImage.createGraphics();
+ g2d.drawImage(transparentImage, 0, 0, null);
+ g2d.dispose();
+
+ // 多轮清理残留背景
+ for (int round = 1; round <= 3; round++) {
+ log.info("执行第{}轮背景清理", round);
+
+ int cleanedPixels = 0;
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int rgb = optimizedImage.getRGB(x, y);
+ int alpha = (rgb >> 24) & 0xFF;
+
+ // 只处理非透明像素
+ if (alpha > 0) {
+ int r = (rgb >> 16) & 0xFF;
+ int g = (rgb >> 8) & 0xFF;
+ int b = rgb & 0xFF;
+
+ // 检查是否应该被清理
+ if (shouldCleanPixel(x, y, r, g, b, optimizedImage, round)) {
+ optimizedImage.setRGB(x, y, 0x00000000); // 设为透明
+ cleanedPixels++;
+ }
+ }
+ }
+ }
+
+ log.info("第{}轮清理完成,清理了{}个像素", round, cleanedPixels);
+
+ // 如果清理的像素很少,说明已经比较干净了
+ if (cleanedPixels < 100) {
+ break;
+ }
+ }
+
+ // 最终验证透明像素比例
+ int finalTransparentCount = 0;
+ for (int y = 0; y < height; y += Math.max(1, height / 50)) {
+ for (int x = 0; x < width; x += Math.max(1, width / 50)) {
+ int rgb = optimizedImage.getRGB(x, y);
+ int alpha = (rgb >> 24) & 0xFF;
+ if (alpha == 0) {
+ finalTransparentCount++;
+ }
+ }
+ }
+
+ log.info("后处理完成,最终透明像素比例: {:.2f}%",
+ (double) finalTransparentCount / ((width / Math.max(1, width / 50)) * (height / Math.max(1, height / 50))) * 100);
+
+ return optimizedImage;
+ }
+
+ /**
+ * 判断像素是否应该被清理
+ *
+ * @param x 像素X坐标
+ * @param y 像素Y坐标
+ * @param r 红色值
+ * @param g 绿色值
+ * @param b 蓝色值
+ * @param image 当前图片
+ * @param round 清理轮次
+ * @return 是否应该清理
+ */
+ private boolean shouldCleanPixel(int x, int y, int r, int g, int b, BufferedImage image, int round) {
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ // 检查周围透明像素的比例
+ int transparentNeighbors = 0;
+ int totalNeighbors = 0;
+
+ for (int dy = -2; dy <= 2; dy++) {
+ for (int dx = -2; dx <= 2; dx++) {
+ if (dx == 0 && dy == 0) continue;
+
+ int nx = x + dx;
+ int ny = y + dy;
+
+ if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
+ int neighborRgb = image.getRGB(nx, ny);
+ int neighborAlpha = (neighborRgb >> 24) & 0xFF;
+
+ if (neighborAlpha == 0) {
+ transparentNeighbors++;
+ }
+ totalNeighbors++;
+ }
+ }
+ }
+
+ double transparentRatio = totalNeighbors > 0 ? (double) transparentNeighbors / totalNeighbors : 0;
+
+ // 根据轮次调整清理策略
+ double threshold = 0.6 - (round - 1) * 0.1; // 逐渐降低阈值
+
+ // 如果周围透明像素比例很高,且当前像素颜色特征像背景,则清理
+ if (transparentRatio > threshold) {
+ // 检查颜色特征
+ int brightness = (r + g + b) / 3;
+ boolean isUniformColor = Math.abs(r - g) < 15 && Math.abs(g - b) < 15 && Math.abs(r - b) < 15;
+ boolean isExtremeBrightness = brightness > 220 || brightness < 30;
+
+ return isUniformColor || isExtremeBrightness;
+ }
+
+ return false;
+ }
+
+ /**
+ * 找到图片的主体边缘
+ *
+ * @param image 图片对象
+ * @return [left, right, top, bottom] 边缘坐标
+ */
+ private int[] findEdgePixels(BufferedImage image) {
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ int leftEdge = width;
+ int rightEdge = 0;
+ int topEdge = height;
+ int bottomEdge = 0;
+
+ // 扫描图片找到非背景像素的边界
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int rgb = image.getRGB(x, y);
+ int r = (rgb >> 16) & 0xFF;
+ int g = (rgb >> 8) & 0xFF;
+ int b = rgb & 0xFF;
+
+ // 判断是否为主体像素(非白色/浅色背景)
+ boolean isSubject = !(r > 240 && g > 240 && b > 240);
+
+ if (isSubject) {
+ if (x < leftEdge) leftEdge = x;
+ if (x > rightEdge) rightEdge = x;
+ if (y < topEdge) topEdge = y;
+ if (y > bottomEdge) bottomEdge = y;
+ }
+ }
+ }
+
+ // 确保边界有效
+ leftEdge = Math.max(0, leftEdge - 5); // 留一些边距
+ rightEdge = Math.min(width - 1, rightEdge + 5);
+ topEdge = Math.max(0, topEdge - 5);
+ bottomEdge = Math.min(height - 1, bottomEdge + 5);
+
+ return new int[]{leftEdge, rightEdge, topEdge, bottomEdge};
+ }
+
+ /**
+ * 检查图片是否包含透明像素
+ *
+ * @param image 图片对象
+ * @return 是否包含透明像素
+ */
+ private boolean hasTransparentPixels(BufferedImage image) {
+ if (!image.getColorModel().hasAlpha()) {
+ return false;
+ }
+
+ int width = image.getWidth();
+ int height = image.getHeight();
+ int sampleSize = Math.min(1000, width * height);
+
+ for (int i = 0; i < sampleSize; i++) {
+ int x = (i * width) / sampleSize;
+ int y = (i * height) / sampleSize;
+ int rgb = image.getRGB(x, y);
+ int alpha = (rgb >> 24) & 0xFF;
+
+ if (alpha < 255) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 验证图片是否满足阿里云要求
+ * 阿里云要求:主体图像必须为带透明背景的RGBA四通道图像,PNG格式,长边不超过2048像素
+ *
+ * @param imageUrl 图片URL
+ * @return 是否满足要求
+ */
+ private boolean validateImageForAliyun(String imageUrl) {
+ try {
+ log.info("开始验证图片是否满足阿里云要求: {}", imageUrl);
+
+ // 1. 下载图片
+ BufferedImage image = downloadImage(imageUrl);
+ if (image == null) {
+ log.error("❌ 无法下载图片");
+ return false;
+ }
+
+ int width = image.getWidth();
+ int height = image.getHeight();
+ log.info("图片尺寸: {}x{}", width, height);
+
+ // 2. 检查尺寸要求(长边不超过2048像素)
+ if (Math.max(width, height) > 2048) {
+ log.error("❌ 图片长边超过2048像素: {}", Math.max(width, height));
+ return false;
+ }
+ log.info("✅ 图片尺寸符合要求(长边: {})", Math.max(width, height));
+
+ // 3. 检查是否为RGBA四通道图像
+ boolean hasAlpha = image.getColorModel().hasAlpha();
+ if (!hasAlpha) {
+ log.error("❌ 图片不支持透明度(非RGBA四通道)");
+ return false;
+ }
+ log.info("✅ 图片支持透明度(RGBA四通道)");
+
+ // 4. 检查图片类型是否为ARGB
+ int imageType = image.getType();
+ if (imageType != BufferedImage.TYPE_INT_ARGB) {
+ log.warn("⚠️ 图片类型不是TYPE_INT_ARGB: {}", getImageTypeName(imageType));
+ // 不强制要求,但记录警告
+ } else {
+ log.info("✅ 图片类型为TYPE_INT_ARGB");
+ }
+
+ // 5. 检查是否包含透明像素
+ boolean hasTransparentPixels = false;
+ int transparentPixelCount = 0;
+ int totalPixels = width * height;
+ int sampleSize = Math.min(1000, totalPixels); // 采样检查
+
+ for (int i = 0; i < sampleSize; i++) {
+ int x = (i * width) / sampleSize;
+ int y = (i * height) / sampleSize;
+ int rgb = image.getRGB(x, y);
+ int alpha = (rgb >> 24) & 0xFF;
+
+ if (alpha < 255) {
+ hasTransparentPixels = true;
+ transparentPixelCount++;
+ }
+ }
+
+ if (!hasTransparentPixels) {
+ log.error("❌ 图片不包含透明像素,不符合阿里云要求");
+ return false;
+ }
+ log.info("✅ 图片包含透明像素,透明像素比例: {:.2f}%",
+ (double) transparentPixelCount / sampleSize * 100);
+
+ // 6. 验证文件格式(通过URL检查)
+ if (!imageUrl.toLowerCase().contains(".png")) {
+ log.error("❌ 图片URL不包含.png扩展名");
+ return false;
+ }
+ log.info("✅ 图片格式为PNG");
+
+ // 7. 检查图片是否可访问
+ try {
+ URL url = new URL(imageUrl);
+ url.openConnection().connect();
+ log.info("✅ 图片URL可访问");
+ } catch (Exception e) {
+ log.error("❌ 图片URL不可访问: {}", e.getMessage());
+ return false;
+ }
+
+ log.info("🎉 图片完全满足阿里云要求!");
+ return true;
+
+ } catch (Exception e) {
+ log.error("验证图片时发生错误", e);
+ return false;
+ }
+ }
+
+ /**
+ * 使用转换后的图片调用阿里云API
+ *
+ * @param convertedImageUrl 转换后的图片URL
+ */
+ private void callAliyunAPIWithConvertedImage(String convertedImageUrl) {
+ try {
+ log.info("开始使用转换后的图片调用阿里云API: {}", convertedImageUrl);
+
+ // 1. 构建请求URL
+ String apiUrl = "https://dashscope.aliyuncs.com/api/v1/services/aigc/background-generation/generation/";
+
+ // 2. 构建请求头
+ HttpHeaders headers = new HttpHeaders();
+ headers.set("X-DashScope-Async", "enable");
+ headers.set("Authorization", "Bearer " + System.getenv("DASHSCOPE_API_KEY"));
+ headers.setContentType(MediaType.APPLICATION_JSON);
+
+ // 2.5. 验证转换后的图片是否满足阿里云要求
+ if (!validateImageForAliyun(convertedImageUrl)) {
+ log.error("转换后的图片不满足阿里云要求,无法继续调用API");
+ return;
+ }
+ log.info("✅ 图片验证通过,满足阿里云要求");
+
+ // 3. 构建请求体(使用转换后的图片URL)
+ Map requestBody = buildRequestBodyWithConvertedImage(convertedImageUrl);
+
+ // 4. 创建HTTP实体
+ HttpEntity