Sfoglia il codice sorgente

报告模板导出签字章处理规则调整

徐滕 1 settimana fa
parent
commit
d4d6e012b7

+ 82 - 10
src/main/java/com/jeeplus/common/utils/ImageUtil.java

@@ -67,8 +67,8 @@ public class ImageUtil {
     // ==================== 签字章图片预处理 ====================
 
     /**
-     * 签字章图片预处理:仅去阴影、去背景、裁剪白边,保持原始尺寸
-     * 保留灰度图的平滑过渡(抗锯齿),不做二值化和缩放
+     * 签字章图片预处理:去阴影、背景透明化、裁剪白边,保持原始尺寸
+     * 去阴影消除光照不均,背景转为透明(适配护眼模式等任意文档底色),输出ARGB PNG
      * @param srcPath 原始图片路径
      * @return 处理后的base64字符串,以及宽高信息(单位pt)
      */
@@ -77,18 +77,20 @@ public class ImageUtil {
         if (original == null) {
             return null;
         }
-        // 1. 转灰度
+        // 1. 转灰度(处理透明背景:透明像素设为白色)
         BufferedImage gray = toGrayscale(original);
-        // 2. 去阴影(背景估算 + 除法归一化)
+        // 2. 去阴影(背景估算 + 除法归一化,消除光照不均)
         BufferedImage normalized = removeShadow(gray);
-        // 3. 裁剪白边(灰度图直接裁剪,保留平滑过渡)
-        BufferedImage cropped = trimWhiteSpace(normalized);
-        // 4. 保持原始尺寸,不做缩放
+        // 3. 背景透明化:亮像素→alpha=0,暗像素→alpha=255
+        BufferedImage transparent = makeBackgroundTransparent(normalized);
+        // 4. 裁剪白边(基于alpha通道检测有效内容)
+        BufferedImage cropped = trimWhiteSpaceAlpha(transparent);
+        // 5. 保持原始尺寸,不做缩放
         int width = cropped.getWidth();
         int height = cropped.getHeight();
-        // 5. 转为base64
+        // 6. 转为base64(PNG格式支持透明通道)
         String base64 = bufferedImageToBase64(cropped, "png");
-        // 6. 计算pt尺寸(1px ≈ 0.75pt)
+        // 7. 计算pt尺寸(1px ≈ 0.75pt)
         float widthPt = width * 0.75f;
         float heightPt = height * 0.75f;
         return new SignImageResult(base64, widthPt, heightPt);
@@ -302,7 +304,77 @@ public class ImageUtil {
             }
         }
         if (!found) {
-            return img; // 全白图,不裁剪
+            // 全白图:返回一个白色小图,避免返回原图导致布局异常
+            BufferedImage blank = new BufferedImage(10, 10, BufferedImage.TYPE_BYTE_GRAY);
+            Graphics2D g = blank.createGraphics();
+            g.setColor(Color.WHITE);
+            g.fillRect(0, 0, 10, 10);
+            g.dispose();
+            return blank;
+        }
+        // 四周留2px边距,避免签字贴边
+        minX = Math.max(0, minX - 2);
+        minY = Math.max(0, minY - 2);
+        maxX = Math.min(w - 1, maxX + 2);
+        maxY = Math.min(h - 1, maxY + 2);
+        return img.getSubimage(minX, minY, maxX - minX + 1, maxY - minY + 1);
+    }
+
+    /**
+     * 背景透明化:将灰度图转为ARGB格式,亮像素→透明,暗像素→不透明
+     * 背景像素(≥240)alpha=0(完全透明),签字笔画保持原色+alpha=255
+     * 中间像素线性过渡alpha值,保留抗锯齿效果
+     */
+    private static BufferedImage makeBackgroundTransparent(BufferedImage gray) {
+        int w = gray.getWidth();
+        int h = gray.getHeight();
+        BufferedImage result = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+        for (int y = 0; y < h; y++) {
+            for (int x = 0; x < w; x++) {
+                int pixel = gray.getRaster().getSample(x, y, 0);
+                int alpha;
+                if (pixel >= 240) {
+                    // 背景区域 → 完全透明
+                    alpha = 0;
+                } else if (pixel <= 200) {
+                    // 签字笔画区域 → 完全不透明
+                    alpha = 255;
+                } else {
+                    // 过渡区域 → 线性过渡alpha,保留抗锯齿
+                    alpha = (int) ((240 - pixel) / 40.0 * 255);
+                }
+                // 保持灰度值,只修改alpha通道
+                result.setRGB(x, y, (alpha << 24) | (pixel << 16) | (pixel << 8) | pixel);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 裁剪白边(基于alpha通道):检测非透明区域的最小包围矩形并裁剪
+     */
+    private static BufferedImage trimWhiteSpaceAlpha(BufferedImage img) {
+        int w = img.getWidth();
+        int h = img.getHeight();
+        int minX = w, minY = h, maxX = 0, maxY = 0;
+        boolean found = false;
+
+        for (int y = 0; y < h; y++) {
+            for (int x = 0; x < w; x++) {
+                int argb = img.getRGB(x, y);
+                int alpha = (argb >> 24) & 0xFF;
+                if (alpha > 10) { // 非透明区域
+                    found = true;
+                    if (x < minX) minX = x;
+                    if (x > maxX) maxX = x;
+                    if (y < minY) minY = y;
+                    if (y > maxY) maxY = y;
+                }
+            }
+        }
+        if (!found) {
+            // 全透明图:返回一个透明小图,避免返回原图导致布局异常
+            return new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
         }
         // 四周留2px边距,避免签字贴边
         minX = Math.max(0, minX - 2);

+ 31 - 1
src/main/java/com/jeeplus/modules/workfullmanage/service/WorkFullManageService.java

@@ -88,6 +88,10 @@ public class WorkFullManageService extends CrudService<WorkFullManageDao, WorkFu
 	@Value("${serverDomain}")
 	private String serverDomain;
 
+	// 最近一次签字章图片的宽高(pt),base64String()调用后可读取
+	private float lastSignWidthPt;
+	private float lastSignHeightPt;
+
 	@Autowired
 	private WorkFullManageDao workFullManageDao;
 	@Autowired
@@ -755,8 +759,12 @@ public class WorkFullManageService extends CrudService<WorkFullManageDao, WorkFu
 		if(org.apache.commons.lang3.StringUtils.isNotBlank(ruralProjectRecords.getProjectMasterId())){
 			String base64String = base64String(ruralProjectRecords.getProjectMasterId());
 			data.put("handSignature3",base64String);
+			data.put("handSignature3Width", String.format("%.1f", lastSignWidthPt));
+			data.put("handSignature3Height", String.format("%.1f", lastSignHeightPt));
 		}else{
 			data.put("handSignature3",blankBase64);
+			data.put("handSignature3Width", "80.2");
+			data.put("handSignature3Height", "50.5");
 		}
 
 		//查询杨荣华杨所的用户信息
@@ -764,8 +772,12 @@ public class WorkFullManageService extends CrudService<WorkFullManageDao, WorkFu
 		if(org.apache.commons.lang3.StringUtils.isNotBlank(yrhUser.getId())){
 			String base64String = base64String(yrhUser.getId());
 			data.put("handSignature1",base64String);
+			data.put("handSignature1Width", String.format("%.1f", lastSignWidthPt));
+			data.put("handSignature1Height", String.format("%.1f", lastSignHeightPt));
 		}else{
 			data.put("handSignature1",blankBase64);
+			data.put("handSignature1Width", "80.2");
+			data.put("handSignature1Height", "50.5");
 		}
 
 		//查询陈红星陈总的用户信息
@@ -773,8 +785,12 @@ public class WorkFullManageService extends CrudService<WorkFullManageDao, WorkFu
 		if(org.apache.commons.lang3.StringUtils.isNotBlank(chxUser.getId())){
 			String base64String = base64String(chxUser.getId());
 			data.put("handSignature2",base64String);
+			data.put("handSignature2Width", String.format("%.1f", lastSignWidthPt));
+			data.put("handSignature2Height", String.format("%.1f", lastSignHeightPt));
 		}else{
 			data.put("handSignature2",blankBase64);
+			data.put("handSignature2Width", "80.2");
+			data.put("handSignature2Height", "50.5");
 		}
 
 		//咨询员信息
@@ -799,6 +815,8 @@ public class WorkFullManageService extends CrudService<WorkFullManageDao, WorkFu
 		data.put("consultantProfession1","");
 		data.put("consultantQualification1","");
 		data.put("handSignature4",blankBase64);
+		data.put("handSignature4Width", "80.2");
+		data.put("handSignature4Height", "39.1");
 
 		Integer zxyFlag = 0;
 		//根据咨询员id查询咨询员信息以及咨询员的专业和资格
@@ -817,9 +835,13 @@ public class WorkFullManageService extends CrudService<WorkFullManageDao, WorkFu
 						if(org.apache.commons.lang3.StringUtils.isNotBlank(consultantUser.getId())){
 							String base64String = base64String(consultantUser.getId());
 							data.put("handSignature4",base64String);
+							data.put("handSignature4Width", String.format("%.1f", lastSignWidthPt));
+							data.put("handSignature4Height", String.format("%.1f", lastSignHeightPt));
 							ruralReportConsultant.setSignatureStampUrl(base64String);
 						}else{
 							data.put("handSignature4","");
+							data.put("handSignature4Width", "80.2");
+							data.put("handSignature4Height", "39.1");
 						}
 						break;
 					}
@@ -829,8 +851,12 @@ public class WorkFullManageService extends CrudService<WorkFullManageDao, WorkFu
 				if(org.apache.commons.lang3.StringUtils.isNotBlank(consultantUser.getId())){
 					String base64String = base64String(consultantUser.getId());
 					data.put("handSignature4",base64String);
+					data.put("handSignature4Width", String.format("%.1f", lastSignWidthPt));
+					data.put("handSignature4Height", String.format("%.1f", lastSignHeightPt));
 				}else{
 					data.put("handSignature4","");
+					data.put("handSignature4Width", "80.2");
+					data.put("handSignature4Height", "39.1");
 				}
 				data.put("zixunyuanName",consultantUser.getName());
 				data.put("consultantProfession1","");
@@ -959,10 +985,12 @@ public class WorkFullManageService extends CrudService<WorkFullManageDao, WorkFu
 					filePath.mkdirs();
 				}
 				new OSSClientUtil().downByStreamSaveLocal(ossKey,fileName,path+fileName);
-				// 对签字章图片进行预处理(去阴影、去背景、二值化、裁剪白边、归一化尺寸)
+				// 对签字章图片进行预处理(去阴影、背景白化、裁剪白边)
 				ImageUtil.SignImageResult result = ImageUtil.processSignImage(path + fileName);
 				if (result != null) {
 					imageStr = result.getBase64();
+					lastSignWidthPt = result.getWidthPt();
+					lastSignHeightPt = result.getHeightPt();
 				}
 			}
 		}catch (Exception e){
@@ -972,6 +1000,8 @@ public class WorkFullManageService extends CrudService<WorkFullManageDao, WorkFu
 		}
 		if(org.apache.commons.lang3.StringUtils.isBlank(imageStr)){
 			imageStr = blankBase64;
+			lastSignWidthPt = 0;
+			lastSignHeightPt = 0;
 		}
 
 

+ 2 - 2
src/main/resources/freemarker/projectSignature.ftl

@@ -1830,7 +1830,7 @@
 						<v:shape id="图片 6"
 								 o:spid="_x0000_s1030"
 								 type="#_x0000_t75"
-								 style="position:absolute;left:0;text-align:left;margin-left:175.5pt;margin-top:27.95pt;width:80.2pt;height:50.45pt;z-index:6;mso-wrap-style:square"
+								 style="position:absolute;left:0;text-align:left;margin-left:175.5pt;margin-top:27.95pt;width:${handSignature3Width!"80.2"}pt;height:${handSignature3Height!"50.5"}pt;z-index:6;mso-wrap-style:square"
 								 wrapcoords="21592 -2 0 0 0 21600 21592 21602 8 21602 21600 21600 21600 0 8 -2 21592 -2">
 							<v:fill o:detectmouseclick="t"/>
 							<w:binData w:name="wordml://03000004.png"
@@ -1944,7 +1944,7 @@
 						<v:shape id="图片 17"
 								 o:spid="_x0000_s1041"
 								 type="#_x0000_t75"
-								 style="position:absolute;left:0;text-align:left;margin-left:173.7pt;margin-top:.8pt;width:80.2pt;height:39.1pt;z-index:9;mso-wrap-style:square">
+								 style="position:absolute;left:0;text-align:left;margin-left:173.7pt;margin-top:.8pt;width:${handSignature4Width!"80.2"}pt;height:${handSignature4Height!"39.1"}pt;z-index:9;mso-wrap-style:square">
 							<v:fill o:detectmouseclick="t"/>
 							<w:binData w:name="wordml://03000005.png"
 									   xml:space="preserve">${handSignature4}

+ 2 - 2
src/main/resources/jeeplus.properties

@@ -159,9 +159,9 @@ endpoint=http://oss-cn-hangzhou.aliyuncs.com
 accessKeyId=LTAI5tKa6kzGr5EyPWJB4EcD
 accessKeySecret=arHxB7ZPhizrBYf4844TtyaRctPMgW
 #\u5F00\u53D1\u73AF\u5883
-#bucketName=xgxm-test
+bucketName=xgxm-test
 #\u751F\u4EA7\u73AF\u5883
-bucketName=xg-ccpm
+#bucketName=xg-ccpm
 #\u01E9\uFFFD\uFFFDbucketName
 qzBucketName=xg-qz
 avatarDir=app-img/avatar/