【Java】如何优雅的用代码制作企业公章

时间:2023-02-05 10:26:38

Graphics2D

根据API上的说法是,在使用Graphics2D类库的时候,这是进行操作的主要类,类似于提供了一种context。Graphics2D为抽象类,继承自Graphics类,所以在操作前,需要先获取一种可以操作的对象,然后再创建Graphics2D对象。 
Graphics2D可以支持三种操作:图形操作(画各种图形,填充)、写文字、变换(比如旋转、切割等)

画圆

再画圆的时候,首先要生成一个Shape对象,然后调用g2d.draw(shape)即可,因此,这里面如何生成Shape对象就很重要了。 可以看一下Shape类结构,shape是一个接口类,实现了众多实现类,比如Arc(弧度)、贝塞尔曲线、椭圆、线等等。这里我们要使用Arc,既然可以生成弧度,那么当然就可以生成圆。

g2d.setPaint(Color.red);//设置画笔
g2d.setStroke(new BasicStroke(5));//设置画笔的粗度
Shape circle = new Arc2D.Double(0,0,circleRadius*2,circleRadius*2,0,360,Arc2D.OPEN);//构造圆形,Arc2D为抽象类,Arc2D.double和Arc2D.float为它的具体实现类。
g2d.draw(circle);//OK,现在已经有了一个圆
BufferedImage bi = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB);//创建了bufferedImage对象,可以在最后保存为图片 Graphics2D g2d = bi.createGraphics();//创建Graphics2D对象

写字

写字看起来最简单,直接调用g2d.drawString()即可了,是的,是这样,但是又不仅仅如此,因为我们常常对这些字的格式有很高的要求。这得让我们仔细思考两个问题。

  1. 从哪里开始绘制?也就是说固定那个坐标,固定字的左上角?左下角?甚至中间?
  2. 字的宽度是多少?有人说不是由font-size决定吗?的确由它决定,但是它的宽度又不等于font-size。

这两个问题的答案是至关重要的,因为如果不搞清楚,我们没办法绘制。 
g2d有一个方法是getFontRenderContext,通过它我们可以获取到字体渲染的上下文环境。然后定义一个Font对象,然后在该对象上调用 
f.getStringBounds()来得到一个Rectangle2D的对象。而这个对象是一个长方形的对象类。Briliant,从字面意思我们也可以知道getStringBounds可以获取到文字的外轮廓所构成的矩形。 
这个矩阵包含如下几个方法:getX()、getY():分别获取矩形左上角的坐标。getHeight()、getWidth():分别获取矩形的高度、宽度。get CenterX()、getCenterY()表示获取矩形的中心点坐标。 
OK,我们来回答上述两个问题 
第一:从哪里开始绘制,我不知道,这个很尴尬,我做了如下实验,这个感觉就好像是以(0,-30)开始绘制一个宽度为40的矩形一样。 
第二:字的宽度,我们可以通过getHeight()来得到字的高度,通过getWidth()来获取字符串的宽度。

int fontSize = 30;
Font f = new Font("宋体", Font.PLAIN, fontSize);
FontRenderContext context = g2d.getFontRenderContext();
Rectangle2D bounds = f.getStringBounds(center, context);

System.out.println("X:"+bounds.getX());//X:0.0
System.out.println("Y:"+bounds.getY());//Y:-30.1611328125
System.out.println("height:"+bounds.getHeight());//height:40.9716796875
System.out.println("center y:"+bounds.getCenterY());//center y:-9.67529296875

那我们如何在我们希望的位置进行绘制呢?很简单,平移。只要将矩形的中心放到我们想要的位置就可以了。因此,我们可以
g2d.drawString(center, (float) (circleRadius - bounds.getCenterX()), (float) (circleRadius - bounds.getCenterY()));

上面就是将文字的中心点平移到(circleRadius,circleRadius)处。


弧形文字

印章上可能最复杂的部分就是在于顶部的弧形文字,android的canvas上是利用一条曲线,然后用一个API可以在这条path上写字。但是java应用并没有这样的库,所以需要自己来处理。这里主要指对文字要进行旋转。 
旋转是这样实现的:

Font f = new Font(...);
f.deriveFont(AffineTransform transform);//AffineTransform就是表示线性变换,

AffineTransform transform = AffineTransform .getRotateInstance(α)//表示正向旋转α度,这里是通过矩阵变换实现的,详细信息请看API

弧形文字知道怎么构建了,下一个问题是如何确定第一个文字的位置? 
这里要分两种情况,当文字个数为奇数时,中间的文字正好在圆心的上方,而当文字个数为偶数时,中间两个文字都正好沿着中间线对称排布。
if(countOfMsg % 2 == 1){//奇数
      firstAngle = (countOfMsg-1)*radianPerInterval/2.0 + Math.PI/2;
}else{//偶数
      firstAngle = (countOfMsg/2.0-1)*radianPerInterval + radianPerInterval/2.0 +Math.PI/2;
}

其中radianPerInterval是指字和字之間的夾角。

代码:

Official1.java

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;


public class Official1{
public static BufferedImage getSeal(String head,String foot,
int canvasWidth,int canvasHeight){
//TYPE_INT_ARGB表示一个图像,它具有合成整数像素的8位RGBA颜色分量
BufferedImage bi = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB);
//创建一个GraphicsD,可以将它绘制到此BufferedImage中
Graphics2D g2 = bi.createGraphics();
/***************画笔**************/
//	g2.setPaint(Color.WHITE);	//设置画笔颜色
//	g2.fillRect(0, 0, canvasWidth, canvasWidth);	//填充颜色
int circleRadius = Math.min(canvasWidth, canvasHeight)/2;	//圆形半径
/***************画圆**************/
g2.setPaint(Color.RED);
g2.setStroke(new BasicStroke(5));	
//设置画笔粗细/Arc2D.Double(指定的位置,大小,角跨越,闭合类型);
Shape circle = new Arc2D.Double(0,0,circleRadius*2,circleRadius*2,0,360,Arc2D.OPEN);
g2.draw(circle);
/***************设置样式**************/
int fontSize = 40;
Font f = new Font("宋体",Font.BOLD,fontSize);	//设置字体样式
FontRenderContext context = g2.getFontRenderContext();	//获取font呈现的上下文
g2.setFont(f);
fontSize = canvasWidth/10;
f = new Font("黑体",Font.BOLD,fontSize);
context = g2.getFontRenderContext();
Rectangle2D bounds = f.getStringBounds(foot,context);
g2.setFont(f);
g2.drawString(foot, (float) (circleRadius - bounds.getCenterX()), (float) (circleRadius*1.7 - bounds.getCenterY()));
/***************center**************/
Font starFont = new Font("宋体", Font.BOLD, canvasWidth/2);
g2.setFont(starFont);
g2.drawString("★",canvasWidth/4, canvasHeight-canvasHeight/3); 
/***************head**************/
fontSize = canvasWidth/10;
f = new Font("宋体",Font.BOLD,fontSize);
context = g2.getFontRenderContext();
bounds = f.getStringBounds(head,context);
double msgWidth = bounds.getWidth();
int countOfMsg = head.length();
double interval = 0 ;//计算间距
if((canvasWidth+canvasHeight)<=400){
interval = msgWidth/(countOfMsg+1);
}else if((canvasWidth+canvasHeight)>=800){
interval = msgWidth/(countOfMsg-2);
}else{
interval = msgWidth/(countOfMsg-1);
}

double newRadius = circleRadius + bounds.getY()-5;//bounds.getY()是负数,这样可以将弧形文字固定在圆内了。-5目的是离圆环稍远一点
double radianPerInterval = 2 * Math.asin(interval / (2 * newRadius));//每个间距对应的角度
//第一个元素的角度
double firstAngle;
if(countOfMsg % 2 == 1){//奇数
firstAngle = (countOfMsg-1)*radianPerInterval/2.0 + Math.PI/2+0.08;
}else{//偶数
firstAngle = (countOfMsg/2.0-1)*radianPerInterval + radianPerInterval/2.0 +Math.PI/2+0.08;
}

for(int i = 0;i<countOfMsg;i++){
double aa = firstAngle - i*radianPerInterval;
double ax = newRadius * Math.sin(Math.PI/2 - aa);//小小的trick,将【0,pi】区间变换到[pi/2,-pi/2]区间
double ay = newRadius * Math.cos(aa-Math.PI/2);//同上类似,这样处理就不必再考虑正负的问题了
AffineTransform transform = AffineTransform .getRotateInstance(Math.PI/2 - aa);// ,x0 + ax, y0 + ay);
Font f2 = f.deriveFont(transform);
g2.setFont(f2);
g2.drawString(head.substring(i,i+1), (float) (circleRadius+ax), (float) (circleRadius - ay));
}

g2.dispose();//销毁资源
return bi;
}
}

Test.java
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;

import javax.imageio.ImageIO;

public class Test {
static Scanner sc = new Scanner(System.in);
public static void main(String[] args) {
System.out.print("请输入图片的宽度:");
int canvasWidth = sc.nextInt();
System.out.print("请输入图片的高度:");
int canvasHeight = sc.nextInt();
System.out.println("请输入图片保存路径:(格式:磁盘:/目录(D:/test.gif))");
String savepath = sc.next();
System.out.print("请输入公司名称:");
String head = sc.next();
companyName(canvasWidth, canvasHeight,head, savepath);
}


public static void companyName(int canvasWidth,int canvasHeight,String savepath,String head){
int length = head.length();
while(true){
if(length >= 2 && length <= 30){
officialType(canvasWidth, canvasHeight,head,savepath);
break;
}else{
System.err.print("公司名称不规范,请重新输入公司名称:");
head = sc.next();
}
}
}
public static void officialType(int canvasWidth,int canvasHeight,String savepath,String head){
System.out.print("请输入公章类别:");
while(true){
String foot = sc.next();
int len = foot.length();
if (len > 0 && len <= 10) {
BufferedImage image = Official1.getSeal(head, foot,
canvasWidth, canvasHeight);

try {
ImageIO.write(image, "GIF", new File(savepath));
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("运行成功");
break;
}
}else
System.err.println("类别名称不规范,请重新输入");
}
}
}