Java 實現圖片合成

圖片合成

利用Java的繪圖方法,實現圖片合成html

在開始以前,先定一個小目標,咱們但願經過圖片合成的方式,建立一個相似下面樣式的圖片java

I. 設計思路

首先解析一下咱們的目標實現圖片合成,那麼這些合成的基本組成單元有些什麼?git

組成基本單元github

  • 圖片
  • 文字
  • 幾何圖形

也就是說,咱們能夠將任意個圖片,文字,幾何圖形,按照本身的意願進行拼接,那麼問題就轉變成兩個spring

  • 基本單元如何在畫布上渲染
  • 基本單元之間如何配合使用

II. 基本單元繪製

首先定義一個基本單元的接口,以後全部組合的元素都繼承自這個接口markdown

接口IMergeCell只定義一個繪製的方法,用於實現該基本單元的繪製方式app

public interface IMergeCell {
    void draw(Graphics2D g2d);
}

1. 圖片繪製

繪製圖片,通常來說須要知道:ide

  • 繪製的座標(x,y)
  • 繪製圖片的寬高(w,h),當目標是繪製原圖時,寬高通常爲圖片自己的寬高

結合上面兩點,圖片組成單元的定義以下: ImgCellspring-boot

@Data
@Builder
public class ImgCell implements IMergeCell {

    private BufferedImage img;

    private Integer x, y, w, h;

    @Override
    public void draw(Graphics2D g2d) {
        if (w == null) {
            w = img.getWidth();
        }

        if (h == null) {
            h = img.getHeight();
        }

        g2d.drawImage(img, x, y, w, h, null);
    }
}

2. 文本繪製

圖片繪製比較簡單,相比而言,文字繪製就麻煩一點,主要是文本繪製的對齊方式,豎排仍是橫排佈局工具

首先分析咱們須要的基本信息

  • 考慮對齊方式(居中對齊,靠左,靠上,靠右,靠下)

    • 所以須要肯定文本繪製的區域,因此須要兩個座標 (startX, startY), (endX, endY)
  • 文本繪製參數

    • 能夠指定字體Font,文本顏色 Color,行間距 lineSpace
  • 繪製的文本信息

    • 文本內容 List<String>

繪製實現

  • 若單行的文本超過長度上限,則須要自動換行,因此有 batchSplitText 方法,對原文本內容進行分割,確保不會超過邊界

  • 不一樣的對齊方式,繪製的起始座標須要計算, 因此在水平佈局文字時,須要經過 calculateX方法獲取新的x座標;豎直佈局文字時,須要經過 calculateY獲取新的y座標

實際代碼以下

@Data
public class TextCell implements IMergeCell {

    private List<String> texts;

    private Color color = Color.black;

    private Font font = FontUtil.DEFAULT_FONT;


    private int lineSpace;

    private int startX, startY;
    private int endX, endY;


    /**
     * 繪製樣式
     */
    private ImgCreateOptions.DrawStyle drawStyle = ImgCreateOptions.DrawStyle.HORIZONTAL;


    private ImgCreateOptions.AlignStyle alignStyle = ImgCreateOptions.AlignStyle.LEFT;


    public void addText(String text) {
        if (texts == null) {
            texts = new ArrayList<>();
        }

        texts.add(text);
    }


    @Override
    public void draw(Graphics2D g2d) {
        g2d.setColor(color);
        g2d.setFont(font);

        FontMetrics fontMetrics = FontUtil.getFontMetric(font);
        int tmpHeight = fontMetrics.getHeight(), tmpW = font.getSize() >>> 1;
        int tmpY = startY, tmpX = startX;
        int offsetX = drawStyle == ImgCreateOptions.DrawStyle.VERTICAL_LEFT
                ? (font.getSize() + fontMetrics.getDescent() + lineSpace)
                : -(font.getSize() + fontMetrics.getDescent() + lineSpace);
        // 單行文本自動換行分割
        List<String> splitText = batchSplitText(texts, fontMetrics);
        for (String info : splitText) {
            if (drawStyle == ImgCreateOptions.DrawStyle.HORIZONTAL) { 
                g2d.drawString(info, calculateX(info, fontMetrics), tmpY);

                // 換行,y座標遞增一位
                tmpY += fontMetrics.getHeight() + lineSpace;
            } else { // 垂直繪製文本
                char[] chars = info.toCharArray();

                tmpY = calculateY(info, fontMetrics);
                for (int i = 0; i < chars.length; i++) {
                    tmpX = PunctuationUtil.isPunctuation(chars[i]) ? tmpW : 0;
                    g2d.drawString(chars[i] + "",
                            tmpX + (PunctuationUtil.isPunctuation(chars[i]) ? tmpW : 0),
                            tmpY);
                    tmpY += tmpHeight;
                }

                // 換一列
                tmpX += offsetX;
            }
        }
    }


    // 若單行文本超過長度限制,則自動進行換行
    private List<String> batchSplitText(List<String> texts, FontMetrics fontMetrics) {
        List<String> ans = new ArrayList<>();
        if (drawStyle == ImgCreateOptions.DrawStyle.HORIZONTAL) {
            int lineLen = Math.abs(endX - startX);
            for(String t: texts) {
                ans.addAll(Arrays.asList(GraphicUtil.splitStr(t, lineLen, fontMetrics)));
            }
        } else {
            int lineLen = Math.abs(endY - startY);
            for(String t: texts) {
                ans.addAll(Arrays.asList(GraphicUtil.splitVerticalStr(t, lineLen, fontMetrics)));
            }
        }
        return ans;
    }


    private int calculateX(String text, FontMetrics fontMetrics) {
        if (alignStyle == ImgCreateOptions.AlignStyle.LEFT) {
            return startX;
        } else if (alignStyle == ImgCreateOptions.AlignStyle.RIGHT) {
            return endX - fontMetrics.stringWidth(text);
        } else {
            return startX + ((endX - startX - fontMetrics.stringWidth(text)) >>> 1);
        }

    }


    private int calculateY(String text, FontMetrics fontMetrics) {
        if (alignStyle == ImgCreateOptions.AlignStyle.TOP) {
            return startY;
        } else if (alignStyle == ImgCreateOptions.AlignStyle.BOTTOM) {
            int size = fontMetrics.stringWidth(text) + fontMetrics.getDescent() * (text.length() - 1);
            return endY - size;
        } else {
            int size = fontMetrics.stringWidth(text) + fontMetrics.getDescent() * (text.length() - 1);
            return startY + ((endY - endX - size) >>> 1);
        }
    }
}

說明:

  • 單行文本的分割,使用了博文系列中的工具方法 GraphicUtil.splitStr,有興趣的關注源碼進行查看
  • 水平佈局時,指望 startX < endX, 從習慣來說,基本上咱們都是從左到右進行閱讀
  • 水平or垂直佈局,都但願是 startY < endY
  • 垂直佈局時,以字符爲單位進行繪製;標點符號的繪製時,x座標有一個偏移量

3. Line直線繪製

幾何圖形之直線繪製,給出起點和結束點座標,繪製一條直線,比較簡單;這裏給出了虛線的支持

@Data
@Builder
public class LineCell implements IMergeCell {

    /**
     * 起點座標
     */
    private int x1, y1;

    /**
     * 終點座標
     */
    private int x2, y2;

    /**
     * 顏色
     */
    private Color color;


    /**
     * 是不是虛線
     */
    private boolean dashed;

    /**
     * 虛線樣式
     */
    private Stroke stroke = CellConstants.LINE_DEFAULT_STROKE;


    @Override
    public void draw(Graphics2D g2d) {
        g2d.setColor(color);
        if (!dashed) {
            g2d.drawLine(x1, y1, x2, y2);
        } else { // 繪製虛線時,須要保存下原有的畫筆用於恢復
            Stroke origin = g2d.getStroke();
            g2d.setStroke(stroke);
            g2d.drawLine(x1, y1, x2, y2);
            g2d.setStroke(origin);
        }
    }
}

4. 矩形框繪製

矩形框繪製,同直線繪製,支持圓角矩形,支持虛線框

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class RectCell implements IMergeCell {

    /**
     * 起始座標
     */
    private int x, y;

    /**
     * 矩形寬高
     */
    private int w, h;


    /**
     * 顏色
     */
    private Color color;


    /**
     * 是否爲虛線
     */
    private boolean dashed;


    /**
     * 虛線樣式
     */
    private Stroke stroke;


    /**
     * 圓角弧度
     */
    private int radius;


    @Override
    public void draw(Graphics2D g2d) {
        g2d.setColor(color);
        if (!dashed) {
            g2d.drawRoundRect(x, y, w, h, radius, radius);
        } else {
            Stroke stroke = g2d.getStroke();
            g2d.setStroke(stroke);
            g2d.drawRoundRect(x, y, w, h, radius, radius);
            g2d.setStroke(stroke);
        }
    }
}

5. 矩形區域填充

@Data
@Builder
public class RectFillCell implements IMergeCell {

    private Font font;

    private Color color;


    private int x,y,w,h;

    @Override
    public void draw(Graphics2D g2d) {
        g2d.setFont(font);
        g2d.setColor(color);;
        g2d.fillRect(x, y, w, h);
    }
}

III. 封裝

上面實現了幾個常見的基本單元繪製,接下來則是封裝繪製, 這塊的邏輯就比較簡單了以下

public class ImgMergeWrapper {
    public static BufferedImage merge(List<IMergeCell> list, int w, int h) {
        BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = GraphicUtil.getG2d(img);
        list.forEach(cell -> cell.draw(g2d));
        return img;
    }
}

IV. 測試

寫了一個模板QrCodeCardTemplateBuilder,用於拼裝上圖的樣式,代碼較長,不貼了,有興趣的查看原圖

測試代碼以下

@Test
public void testTemplate() throws IOException {
    BufferedImage logo = ImageUtil.getImageByPath("logo.jpg");
    BufferedImage qrCode = ImageUtil.getImageByPath("/Users/yihui/Desktop/12.jpg");
    String name = "小灰灰blog";
    List<String> desc = Arrays.asList("我是一灰灰,一匹不吃羊的狼   專一碼農技術分享");


    int w = QrCodeCardTemplate.w, h = QrCodeCardTemplate.h;
    List<IMergeCell> list = QrCodeCardTemplateBuilder.build(logo, name, desc, qrCode, "微 信 公 衆 號");
    
    BufferedImage bg = ImgMergeWrapper.merge(list, w, h);
    
    try {
        ImageIO.write(bg, "jpg", new File("/Users/yihui/Desktop/merge.jpg"));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

演示圖以下:

演示case

V. 其餘

項目地址:

系列博文

掃描關注,java分享

相關文章
相關標籤/搜索