本文提供一個基於 SpringMVC + JSP 的完整圖片文字水印實現方案,涵蓋項目配置、控制器、服務層、水印核心算法及前端頁面,適合 Java Web 開發者快速集成水印功能。
Graphics2D, AlphaComposite) + JAI (JPEG 編碼)<web-app version="3.1" xmlns="https://xmlns.jcp.org/xml/ns/javaee">
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:mvc="https://www.springframework.org/schema/mvc"
xmlns:context="https://www.springframework.org/schema/context">
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
<context:component-scan base-package="com.wenteryan"/>
<!-- 視圖解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"></property>
<property name="suffix" value=".jsp"></property>
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property>
</bean>
<!-- 文件上傳解析器:支援 10GB 大文件 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"/>
<property name="maxUploadSize" value="10485760000"/>
<property name="maxInMemorySize" value="40960"/>
</bean>
</beans>
@Controller
public class WatermarkAction {
@Autowired private MarkService markService;
@Autowired private UploadService uploadService;
@RequestMapping(value="/watermark", method=RequestMethod.POST)
public ModelAndView watermark(@RequestParam("image") CommonsMultipartFile file,
HttpSession session) throws Exception {
String uploadPath = "/images";
String realPath = session.getServletContext().getRealPath(uploadPath);
// 1. 保存原圖
String originalUrl = uploadService.uploadImage(file, uploadPath, realPath);
// 2. 生成水印圖
String watermarkUrl = markService.watermark(file, uploadPath, realPath);
ModelAndView mv = new ModelAndView("watermark");
mv.addObject("imageUrl", originalUrl);
mv.addObject("logoImageUrl", watermarkUrl);
return mv;
}
}
public interface MarkService {
String MARK_TEXT = "wenteryan"; // 水印文字
String FONT_NAME = "微軟雅黑"; // 字體
int FONT_SIZE = 120; // 字號
int FONT_STYLE = Font.BOLD; // 樣式
Color FONT_COLOR = Color.RED; // 顏色
int X = 10, Y = 10; // 起始座標
float ALPHA = 0.3F; // 透明度 (0.0-1.0)
String watermark(CommonsMultipartFile file, String uploadPath, String realPath);
}
@Service
public class UploadServiceImpl implements UploadService {
@Override
public String uploadImage(CommonsMultipartFile file, String uploadPath, String realPath) {
try (InputStream is = file.getInputStream();
OutputStream os = new FileOutputStream(realPath + "/" + file.getOriginalFilename())) {
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) > 0) {
os.write(buffer, 0, len);
}
} catch (IOException e) {
throw new RuntimeException("文件上傳失敗", e);
}
return uploadPath + "/" + file.getOriginalFilename();
}
}
@Service
public class MarkServiceImpl implements MarkService {
@Override
public String watermark(CommonsMultipartFile file, String uploadPath, String realPath) {
String logoName = "logo_" + file.getOriginalFilename();
File outputFile = new File(realPath, logoName);
try (InputStream is = file.getInputStream();
OutputStream os = new FileOutputStream(outputFile)) {
// 1. 讀取原圖
BufferedImage srcImg = ImageIO.read(is);
int width = srcImg.getWidth();
int height = srcImg.getHeight();
// 2. 創建畫布
BufferedImage buffered = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = buffered.createGraphics();
g.drawImage(srcImg, 0, 0, width, height, null);
// 3. 設置水印樣式
g.setFont(new Font(FONT_NAME, FONT_STYLE, FONT_SIZE));
g.setColor(FONT_COLOR);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, ALPHA));
// 4. 計算文字寬度並繪製 (居中偏移邏輯)
int textWidth = FONT_SIZE * getTextLength(MARK_TEXT);
int x = Math.min(X, width - textWidth);
int y = Math.min(Y + FONT_SIZE, height); // 基線座標
g.drawString(MARK_TEXT, x, y);
g.dispose();
// 5. 輸出 JPEG (使用 ImageIO 替代過時的 JPEGCodec)
ImageIO.write(buffered, "JPEG", os);
} catch (IOException e) {
throw new RuntimeException("水印生成失敗", e);
}
return uploadPath + "/" + logoName;
}
/** 計算文字顯示寬度(中文算 2 個字符寬度) */
private int getTextLength(String text) {
int len = text.length();
for (char c : text.toCharArray()) {
if (String.valueOf(c).getBytes().length > 1) len++; // 雙字節字符
}
return (len % 2 == 0) ? len / 2 : len / 2 + 1;
}
}
<form action="watermark" method="post" enctype="multipart/form-data">
<h2>請選擇上傳的圖片</h2>
<input type="file" name="image" id="image" required/>
<button type="submit" class="btn btn-success">開始上傳並加水印</button>
</form>
<div class="panel-body">
<h4>原圖</h4>
<img src="${pageContext.request.contextPath}${imageUrl}" class="img-responsive"/>
<h4>水印圖</h4>
<img src="${pageContext.request.contextPath}${logoImageUrl}" class="img-responsive"/>
<a href="${pageContext.request.contextPath}/" class="btn btn-warning">返回</a>
</div>
Font.createFont 動態加載。com.sun.image.codec.jpeg.JPEGCodec 屬於 Sun 專有 API,已過時。建議改用標準 ImageIO.write(image, "JPEG", outputStream)。AlphaComposite.SRC_ATOP 僅在目標有像素處顯示源像素,配合透明度實現半透明水印效果。BufferedImage)、九宮格平鋪、對角線排列等高級效果。