强烈推荐一个大神的人工智能的教程:http://www.captainai.net/zhanghan
图片加水印与上传
一、效果图
1.未添加水印图
2.添加水印效果图
二、需求场景
在上传的图片上需要加上时间水印
三、加水印
在上代码前有几个问题:
1. 图片上设置水印,字体如何设置?
答:使用java.awt.FONT,这个类支持多少种字体?可以通过下列方法查询都有哪些字体,然后设置自己业务需要的字体
/**
* 支持多少种字体
*
* @param args
*/
public static void main(String[] args) {
GraphicsEnvironment grapEnv = GraphicsEnvironment.getLocalGraphicsEnvironment();
String[] fontNameList = grapEnv.getAvailableFontFamilyNames();
for (String fontName : fontNameList) {
System.out.println(fontName);
}
}
2.图片有大有小,如何使水印大小自适应
答:分别计算图片宽、高和水印宽、高,若水印宽或高超出图片的宽或高,则按照一边的比例做等比缩放,参考代码中的方法:getWaterMarkMetrics
3.水印位置如何设置
答:水印位置有左上角,右上角,左下角,右下角,中间
4.图片本身颜色各异,水印如何支持在所有图片上都可以清晰展示?
答:例如图片底色为黑色,水印为黑色,则会导致水印展示不出来;解决方案是确定一个合理的水印颜色,给水印添加一个背景,例如水印为黑色,那就给水印加一个白色的背景,白色整个覆盖到图片上会覆盖原图的内容,通过设置透明度实现添加的水印不覆盖原图内容
5.废话不多说,直接上代码
// 300像素
private final static Integer PIXEL_300 = 300;
// 20像素
private final static Integer PIXEL_20 = 20;
private final static String WATER_MARK_WIDTH = "waterMarkWidth";
private final static String WATER_MARK_HEIGHT = "waterMarkHeight";
private final static String WATER_MARK_LOCATION_X = "x";
private final static String WATER_MARK_LOCATION_Y = "y";
/**
* 添加文字水印
*
* @param inputStream 输入图片流
* @param waterMarkContent 水印文字
* @param location 水印位置: 1、左上角,2、右上角,3、左下角,4、右下角,5、中间
* @param degree 旋转角度
* @param alpha 水印透明度
*
* @return
*/
public static InputStream buildWatermarkOfText(InputStream inputStream, String waterMarkContent,
WaterMarkLocation location,
Integer degree, float alpha) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream();) {
Image srcImg = ImageIO.read(inputStream);
if (srcImg != null) {
//获取图片的宽
int srcImgWidth = srcImg.getWidth(null);
//获取图片的高
int srcImgHeight = srcImg.getHeight(null);
// 加水印
BufferedImage bufImg = new BufferedImage(srcImgWidth, srcImgHeight, BufferedImage
.TYPE_INT_RGB);
//获取Graphics2D
Graphics2D g = bufImg.createGraphics();
int fontSize = srcImgWidth < PIXEL_300 ? 1 * PIXEL_20 : srcImgWidth / PIXEL_300 * PIXEL_20;
//水印字体
Font font = new Font("Times", Font.BOLD, fontSize);
//设置字体
g.setFont(font);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawImage(srcImg, 0, 0, srcImgWidth, srcImgHeight, null);
if (null != degree) {
// 设置水印旋转
g.rotate(Math.toRadians(degree), (double) srcImgWidth / 2, (double) srcImgHeight / 2);
}
// 如果水印图片高或宽大于目标图片时做的处理,使水印宽或高等于目标图片的宽高,并且等比例缩放
FontMetrics fontMetrics = g.getFontMetrics(font);
Map<String, Integer> waterMarkMetrics =
getWaterMarkMetrics(fontMetrics, waterMarkContent, srcImgWidth, srcImgHeight);
int newSyWidth = waterMarkMetrics.get(WATER_MARK_WIDTH);
int newSyHeight = waterMarkMetrics.get(WATER_MARK_HEIGHT);
//设置水印位置
Map<String, Integer> waterMarkLocation =
getWaterMarkLocation(location, srcImgWidth, srcImgHeight, newSyWidth, newSyHeight);
int x = waterMarkLocation.get(WATER_MARK_LOCATION_X);
int y = waterMarkLocation.get(WATER_MARK_LOCATION_Y);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
g.drawString(waterMarkContent, x, y);
//设置文字水印阴影
//设置背景色
g.setBackground(Color.WHITE);
g.setColor(Color.white);
g.fillRect(x, y - newSyHeight + 10, newSyWidth, newSyHeight);
//通过使用当前绘图表面的背景色进行填充来清除指定的矩形。
g.clearRect(0, 0, x, y);
g.setPaint(Color.black);
g.drawString(waterMarkContent, x, y);
g.dispose();
ImageIO.write(bufImg, "jpg", os);
bufImg.flush();
InputStream input = new ByteArrayInputStream(os.toByteArray());
return input;
}
} catch (IOException e) {
e.printStackTrace();
}//文件转化为图片
catch (Exception e) {
log.info("上传图片失败{}", e.getMessage());
}
return null;
}
/**
* 获取水印宽高:处理水印超过原图片大小后,需要等比缩放水印大小
*
* @param fontMetrics 字体源数据
* @param waterMarkContent 水印
* @param srcWidth 原图片宽
* @param srcHeight 原图片高
*
* @return
*/
private static Map<String, Integer> getWaterMarkMetrics(FontMetrics fontMetrics, String waterMarkContent,
int srcWidth, int srcHeight) {
Map<String, Integer> metricsMap = new HashMap<>();
int waterMarkWidth = fontMetrics.stringWidth(waterMarkContent);
int waterMarkHeight = fontMetrics.getHeight();
int newSyWidth = waterMarkWidth;
int newSyHeight = waterMarkHeight;
if (waterMarkWidth > srcWidth) {
newSyWidth = srcWidth;
newSyHeight = (int) ((double) newSyWidth / waterMarkWidth * srcHeight);
}
if (newSyHeight > srcHeight) {
newSyHeight = srcHeight;
newSyWidth = (int) ((double) newSyHeight / waterMarkHeight * newSyWidth);
}
metricsMap.put(WATER_MARK_WIDTH, newSyWidth);
metricsMap.put(WATER_MARK_HEIGHT, newSyHeight);
return metricsMap;
}
/**
* 获取水印位置
*
* @param location 水印位置: 1、左上角,2、右上角,3、左下角,4、右下角,5、中间
* @param srcWidth 原图片宽度
* @param srcHeight 原图片高度
* @param newSyWidth 水印宽度
* @param newSyHeight 水印高度
*
* @return
*/
private static Map<String, Integer> getWaterMarkLocation(WaterMarkLocation location, int srcWidth, int srcHeight,
int newSyWidth,
int newSyHeight) {
Map<String, Integer> locationMap = new HashMap<>();
// 根据位置参数确定坐标位置
int x = 0, y = 0;
// location 位置: 1、左上角,2、右上角,3、左下角,4、右下角,5、中间
switch (location.getLocation()) {
case 1:
break;
case 2:
x = srcWidth - newSyWidth;
break;
case 3:
y = srcHeight - newSyHeight;
break;
case 4:
x = srcWidth - newSyWidth;
y = srcHeight - newSyHeight;
break;
case 5:
x = (srcWidth - newSyWidth) / 2;
y = (srcHeight - newSyHeight) / 2;
break;
default:
break;
}
locationMap.put(WATER_MARK_LOCATION_X, x);
locationMap.put(WATER_MARK_LOCATION_Y, y);
return locationMap;
}
/**
* 相对位置
*/
public enum WaterMarkLocation {
/**
* 西北,左上角
*/
NORTH_WEST(1),
/**
* 东北,右上角
*/
NORTH_EAST(2),
/**
* 西南,左下角
*/
SOUTH_WEST(3),
/**
* 东南,右下角
*/
SOUTH_EAST(4),
/**
* 中部
*/
CENTER(5);
private Integer location;
WaterMarkLocation(Integer location) {
this.location = location;
}
public Integer getLocation() {
return location;
}
public void setLocation(Integer location) {
this.location = location;
}
}
6.加水印示例
给图片加时间水印,水印位置设置在左下角,设置0.5f的透明度,效果如本文开头的效果图。
/**
* 加水印+上传
*
* @param file 文件
*
* @return 文件的访问地址
*/
private static String uploadFile(MultipartFile file) {
try {
InputStream inputStream = file.getInputStream();
//水印内容
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
String waterMarkContent = sdf.format(new Date());
//水印位置设置在左下角,设置0.5f的透明度
inputStream = buildWatermarkOfText(inputStream, waterMarkContent, WaterMarkLocation.SOUTH_WEST, null, 0.5f);
//TODO:调用上传
return fileName;
} catch (Exception e) {
log.error("{}", "上传文件失败");
throw new RuntimeException("上传文件失败");
}
}
四、上传
图片存储我们使用的是oss,先添加oss的maven依赖
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
上传图片
@Slf4j
public class OSSUtil {
//OSS 的地址
private final static String OSS_END_POINT = "XXX";
//OSS 的key值
private final static String OSS_ACCESS_KEY_ID = "XXX";
//OSS 的secret值
private final static String OSS_ACCESS_KEY_SECRET = "XXX";
//OSS 的bucket名字
private final static String OSS_BUCKET_NAME = "XXX";
//设置URL过期时间为10年
private final static Date OSS_URL_EXPIRATION = DateUtils.addDays(new Date(), 365 * 10);
// 300像素
private final static Integer PIXEL_300 = 300;
// 20像素
private final static Integer PIXEL_20 = 20;
private final static String WATER_MARK_WIDTH = "waterMarkWidth";
private final static String WATER_MARK_HEIGHT = "waterMarkHeight";
private final static String WATER_MARK_LOCATION_X = "x";
private final static String WATER_MARK_LOCATION_Y = "y";
private volatile static OSSClient instance;
private OSSUtil() {
}
/**
* 单例
*
* @return OSS工具类实例
*/
private static OSSClient getOSSClient() {
if (instance == null) {
synchronized(OSSUtil.class) {
if (instance == null) {
instance = new OSSClient(OSS_END_POINT, OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET);
}
}
}
return instance;
}
/**
* 当Bucket不存在时创建Bucket
* <p>
* Bucket命名规则:
* 1.只能包含小写字母、数字和短横线,
* 2.必须以小写字母和数字开头和结尾
* 3.长度在3-63之间
*/
private static void createBucket() {
try {
if (!OSSUtil.getOSSClient().doesBucketExist(OSS_BUCKET_NAME)) {//判断是否存在该Bucket,不存在时再重新创建
OSSUtil.getOSSClient().createBucket(OSS_BUCKET_NAME);
}
} catch (Exception e) {
log.error("{}", "创建Bucket失败,请核对Bucket名称(规则:只能包含小写字母、数字和短横线,必须以小写字母和数字开头和结尾,长度在3-63之间)");
throw new RuntimeException("创建Bucket失败,请核对Bucket名称(规则:只能包含小写字母、数字和短横线,必须以小写字母和数字开头和结尾,长度在3-63之间)");
}
}
/**
* 上传到OSS服务器 如果同名文件会覆盖服务器上的
*
* @param file 文件
* @param fileDir 上传到OSS上文件的路径,例如test/
*
* @return 文件的访问地址
*/
private static String uploadFile(MultipartFile file, FileDirType fileDir) {
String fileName = String.format(
"%s.%s",
UUID.randomUUID().toString(),
FilenameUtils.getExtension(file.getOriginalFilename()));
try {
InputStream inputStream = file.getInputStream();
//水印内容
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
String waterMarkContent = sdf.format(new Date());
//添加水印
inputStream = buildWatermarkOfText(inputStream, waterMarkContent, WaterMarkLocation.SOUTH_WEST, null, 0.5f);
//创建上传Object的Metadata
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(inputStream.available());
objectMetadata.setCacheControl("no-cache");
objectMetadata.setHeader("Pragma", "no-cache");
objectMetadata.setContentType(getContentType(FilenameUtils.getExtension("." + file.getOriginalFilename())));
objectMetadata.setContentDisposition("inline;filename=" + fileName);
//上传文件
PutObjectResult putResult = OSSUtil.getOSSClient()
.putObject(OSS_BUCKET_NAME, fileDir.getDir() + fileName, inputStream, objectMetadata);
//设置文件读写权限
OSSUtil.getOSSClient().setObjectAcl(OSS_BUCKET_NAME, fileDir.getDir() + fileName,
CannedAccessControlList.PublicRead);
return fileName;
} catch (Exception e) {
log.error("{}", "上传文件失败");
throw new RuntimeException("上传文件失败");
}
}
//文件路径的枚举
public enum FileDirType {
TEST("test/");
private String dir;
FileDirType(String dir) {
this.dir = dir;
}
@JsonValue
public String getDir() {
return dir;
}
}
}