Java生成微信分享海報【基礎設計】

前言

微信後臺生成海報通常都是一個模板寫死,而後就完事了,過了不久讓修改個模板,就又要看半天,還要考慮是否從新複製一份改一改,愈來愈多的重複代碼,全在一個圖片類裏,而後就愈來愈亂。這兩天用設計模式處理了一下,讓之後修改模板,新增模板更舒服一點。有第三方好用的輕量級的實現,還請留言。感激!!java

起步

  • 瞭解IO
  • 瞭解awt
  • 裝飾者設計模式

開始

  • demo地址

喜歡直接看項目的能夠直接 >> demo-commongit

  • 目錄結構

目錄結構

抽象層(abst目錄)

  • 海報抽象類
/**
 * 海報抽象類
 * @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;

}

具體海報特有屬性(content目錄)

/**
 * 朋友小卡片海報
 * @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;
    }

}

裝飾者實現代碼(decorators目錄)

  • 背景裝飾
/**
 * 背景裝飾
 * @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;
    }

}

繪製具體類型海報(draw目錄)

/**
 * @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;
    }
}

效果圖

效果圖

相關文章
相關標籤/搜索