微信後臺生成海報通常都是一個模板寫死,而後就完事了,過了不久讓修改個模板,就又要看半天,還要考慮是否從新複製一份改一改,愈來愈多的重複代碼,全在一個圖片類裏,而後就愈來愈亂。這兩天用設計模式處理了一下,讓之後修改模板,新增模板更舒服一點。有第三方好用的輕量級的實現,還請留言。感激!!java
喜歡直接看項目的能夠直接 >> demo-commongit
/** * 海報抽象類 * @author quaint * @date 21 February 2020 * @since master */ @Data @AllArgsConstructor @NoArgsConstructor public abstract class AbstractPoster implements Poster { /** * 背景圖 */ protected BufferedImage backgroundImage; /** * logo */ protected BufferedImage logo; /** * 廣告語 */ protected String slogan; /** * 主圖 */ protected BufferedImage mainImage; /** * 二維碼 */ protected BufferedImage qrcode; }
/** * 海報裝飾抽象類 * @author quaint * @date 21 February 2020 * @since master */ @Data @AllArgsConstructor public abstract class AbstractPosterDecorator implements Poster { protected Poster poster; protected int positionX; protected int positionY; protected int width; protected int height; public AbstractPosterDecorator(Poster poster){ this.poster = poster; } @Override public BufferedImage draw(BufferedImage image) { System.out.println("默認繪製方法"); return poster.draw(image); } }
/** * 海報接口定義 * @author quaint * @date 21 February 2020 * @since master */ public interface Poster { /** * 畫海報 * @param image image * @return image */ BufferedImage draw(BufferedImage image); }
/** * 海報繪製接口定義 * @author quaint * @date 21 February 2020 * @since master */ public interface PosterDraw<T>{ /** * 是否支持 * @param clazz class * @return bool */ boolean support(Class<?> clazz); /** * 經過數據繪製 並返回 圖片 * @param data 海報所需的數據 * @return 圖片 * @throws IOException io */ BufferedImage drawAndReturnImage(T data) throws IOException; }
/** * 朋友小卡片海報 * @author quaint * @date 21 February 2020 * @since master */ @EqualsAndHashCode(callSuper = true) @Data @NoArgsConstructor public class MiniAppCardPoster extends AbstractPoster { /** * 用戶頭像 */ private BufferedImage headImage; /** * 用戶暱稱 */ private String userNickName; /** * 價格範圍 */ private String priceRange; /** * 劃線價 */ private String linePrice; @Builder(toBuilder = true) public MiniAppCardPoster(BufferedImage backgroundImage, BufferedImage logo, String slogan, BufferedImage mainImage, BufferedImage qrcode, BufferedImage headImage, String userNickName, String priceRange, String linePrice) { super(backgroundImage, logo, slogan, mainImage, qrcode); this.headImage = headImage; this.userNickName = userNickName; this.linePrice = linePrice; this.priceRange = priceRange; } @Override public BufferedImage draw(BufferedImage image) { return image; } }
/** * 背景裝飾 * @author quaint * @date 21 February 2020 * @since master */ public class BackgroundDecorator extends AbstractPosterDecorator { public BackgroundDecorator(Poster poster) { super(poster); } @Builder(toBuilder = true) public BackgroundDecorator(Poster poster, int positionX, int positionY, int width, int height) { super(poster,positionX,positionY,width,height); } @Override public BufferedImage draw(BufferedImage image) { // 繪製 被裝飾以前的 圖片 BufferedImage draw = poster.draw(image); // 裝飾, 繪製頭像 return drawBackground(draw); } /** * 繪製背景具體實現 * @param image image * @return image */ private BufferedImage drawBackground(BufferedImage image){ // 若是寬度沒變化 if (width == image.getWidth() && height == image.getHeight()){ return image; } // 調整背景寬度 if (width!=0 && height !=0){ BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR); Graphics2D g = newImage.createGraphics(); g.drawImage(image,0,0,width,height,null); g.dispose(); return newImage; } // 繪製背景 return image; } }
/** * 繪製 圖片 * @author quaint * @date 21 February 2020 * @since master */ @EqualsAndHashCode(callSuper = true) @Data public class ImageDecorator extends AbstractPosterDecorator { /** * 要繪製的圖片 */ private BufferedImage image; /** * 是否修改成圓形 */ private boolean circle; public ImageDecorator(Poster poster) { super(poster); } @Builder(toBuilder = true) public ImageDecorator(Poster poster, int positionX, int positionY, int width, int height, BufferedImage image, boolean circle) { super(poster,positionX,positionY,width,height); this.image = image; this.circle = circle; } @Override public BufferedImage draw(BufferedImage image) { // 繪製 被裝飾以前的 圖片 BufferedImage draw = poster.draw(image); // 裝飾, 繪製頭像 return drawImage(draw); } /** * 繪製圖片具體實現 * @param sourceImage sourceImage * @return image */ private BufferedImage drawImage(BufferedImage sourceImage){ if (image == null){ return sourceImage; } // 實現繪製圖片 Graphics2D g = sourceImage.createGraphics(); if (circle){ // 設置形狀 Ellipse2D.Double shape = new Ellipse2D.Double(0, 0, image.getWidth(), image.getHeight()); BufferedImage output = new BufferedImage(image.getWidth(),image.getHeight(),BufferedImage.TYPE_4BYTE_ABGR); Graphics2D g2 = output.createGraphics(); output = g2.getDeviceConfiguration().createCompatibleImage(image.getWidth(), image.getHeight(), Transparency.TRANSLUCENT); g2 = output.createGraphics(); // 將背景設置爲透明。若是註釋該段代碼,默認背景爲白色.也可經過g2.setPaint(paint) 設置背景色 g2.setComposite(AlphaComposite.Clear); g2.fill(new Rectangle(image.getWidth(), image.getHeight())); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1)); g2.setClip(shape); // 使用 setRenderingHint 設置抗鋸齒 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.drawImage(image, 0, 0, null); g2.dispose(); image = output; } g.drawImage(image, positionX, positionY, width, height, null); g.dispose(); return sourceImage; } }
/** * 繪製文本 * @author quaint * @date 21 February 2020 * @since master */ @EqualsAndHashCode(callSuper = true) @Data public class TextDecorator extends AbstractPosterDecorator { /** * 字體 */ private Font font = new Font(null); /** * 字體樣式 */ private int fontStyle = Font.PLAIN; /** * 字體大小 */ private int fontSize = 16; /** * 字體顏色 */ private Color color = new Color(255,255,255); /** * 內容 */ private String content; /** * 是否包含刪除先 */ private boolean delLine = false; public TextDecorator(Poster poster) { super(poster); } @Builder(toBuilder = true) public TextDecorator(Poster poster, int positionX, int positionY, int width, int height, Font font, int fontSize, Color color, String content, int fontStyle, boolean delLine) { super(poster,positionX,positionY,width,height); this.font = font; this.fontSize = fontSize; this.color = color; this.content = content; this.fontStyle = fontStyle; this.delLine = delLine; } @Override public BufferedImage draw(BufferedImage image) { // 繪製 被裝飾以前的 圖片 BufferedImage draw = poster.draw(image); // 裝飾, 繪製文本 return drawText(draw); } /** * 繪製文本具體實現 * @param image image * @return image */ private BufferedImage drawText(BufferedImage image){ if (StringUtils.isEmpty(content)){ return image; } // 實現繪製文本 font = font.deriveFont(fontStyle, fontSize); Graphics2D g = image.createGraphics(); // 設置文字抗鋸齒算法 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING , RenderingHints.VALUE_ANTIALIAS_ON); g.setFont(font); g.setColor(color); g.drawString(content, positionX, positionY+fontSize); if (delLine){ // 計算非漢字長度 int shortNum = content.replaceAll("[^0-9,a-z,A-Z,.]", "").length(); // 漢字長度 int longNum = content.length()-shortNum; // 刪除線長度 = (漢字長度 * size) + ((字符長度+1) * size/2) int num = longNum + (shortNum+1)/2; g.drawLine(positionX-fontSize/3,positionY+3*fontSize/5,positionX+fontSize*num,positionY+3*fontSize/5); } g.dispose(); return image; } }
/** * @author quaint * @date 21 February 2020 * @since master */ @Component @Slf4j public class MiniAppCardDraw implements PosterDraw<MiniAppCardPoster> { @Override public boolean support(Class<?> clazz) { if (clazz == null){ return false; } return clazz.equals(MiniAppCardPoster.class); } @Override public BufferedImage drawAndReturnImage(MiniAppCardPoster poster) throws IOException{ log.info("[drawAndReturnImage] method start, param:[{}]", poster); // ======= 主邏輯開始 --> ======= // 1. 繪製背景 最好取背景的 width 和 height BackgroundDecorator drawBg = new BackgroundDecorator(poster).toBuilder() .width(420).height(336).build(); // 2. 繪製頭像 ImageDecorator drawHead = new ImageDecorator(drawBg).toBuilder() .positionX(27).positionY(27) .width(36).height(36) .circle(true) .image(poster.getHeadImage()).build(); // 3. 繪製暱稱 TextDecorator drawNickName = new TextDecorator(drawHead).toBuilder() .positionX(71).positionY(32) .fontSize(18) .content(poster.getUserNickName()+" 向你推薦").build(); // 3. 繪製商品介紹 TextDecorator drawSlogan = new TextDecorator(drawNickName).toBuilder() .positionX(27).positionY(70) .fontSize(22).fontStyle(Font.BOLD) .content(poster.getSlogan()).build(); // 4. 繪製商品圖片 ImageDecorator drawProdImg = new ImageDecorator(drawSlogan).toBuilder() .positionX(24).positionY(129) .width(168).height(168) .image(poster.getMainImage()).build(); // 5. 繪製價格返回 TextDecorator drawPriceRange = new TextDecorator(drawProdImg).toBuilder() .positionX(203).positionY(155) .fontSize(24).fontStyle(Font.BOLD) .color(new Color(216,11,42)) .content(poster.getPriceRange()).build(); // 6. 繪製刪除線價格 TextDecorator drawLinePrice = new TextDecorator(drawPriceRange).toBuilder() .positionX(240).positionY(187) .fontSize(18).delLine(true) .color(new Color(153,153,153)) .content(poster.getLinePrice()).build(); // 調用最後一個包裝類的 draw 方法 BufferedImage drawResult = drawLinePrice.draw(poster.getBackgroundImage()); // ======= <-- 主邏輯結束 ======= log.info("[drawAndReturnImage] method end, result:[success]"); return drawResult; } }