758 lines
28 KiB
Java
758 lines
28 KiB
Java
package com.rj.utils;
|
||
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.springframework.stereotype.Component;
|
||
|
||
import javax.imageio.ImageIO;
|
||
import java.awt.*;
|
||
import java.awt.image.BufferedImage;
|
||
import java.io.*;
|
||
import java.net.URL;
|
||
import java.util.List;
|
||
import java.util.ArrayList;
|
||
|
||
|
||
|
||
/**
|
||
* 图像转换工具类
|
||
*
|
||
* 提供图像格式转换、背景移除、透明度处理等功能
|
||
* 专门用于满足阿里云背景生成API的图片要求:
|
||
* - 主体图像必须为带透明背景的RGBA四通道图像
|
||
* - PNG格式,长边不超过2048像素
|
||
* - 公网可访问的URL
|
||
*
|
||
* @author 成
|
||
* @date 2025/1/30
|
||
* @version 1.0
|
||
*/
|
||
@Slf4j
|
||
@Component
|
||
public class ImageConversionUtil {
|
||
|
||
/**
|
||
* 高级图片转换方法,专门处理透明背景需求
|
||
* 确保生成符合阿里云要求的RGBA四通道PNG图像
|
||
*
|
||
* @param imageUrl 原始图片URL
|
||
* @return 转换后的图片字节数组
|
||
*/
|
||
public 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;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 下载图片
|
||
*
|
||
* @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 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 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 图片对象
|
||
* @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;
|
||
}
|
||
|
||
/**
|
||
* 创建带透明背景的图片(专门处理无透明背景的图片)
|
||
* 使用高级背景移除算法,确保彻底移除背景
|
||
*
|
||
* @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<Color> 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};
|
||
}
|
||
|
||
} |