系列文章:



一 背景问题

本系列旨在分享一些word操作框架POI的一些使用技巧,系统学习可直接参考官方文档,或上一篇中提到的Apache POI Word(docx) 入门示例教程。更多交流可添加公众号【程序员架构进阶】一起探讨。

本篇是根据一个真实场景,探索的实现方法。我们有一些希望插入word的图片,在插入时也要对图片本身添加一些标注,例如红框标记等等,也可能是添加文字标注。如果是手工操作,这显然比较简单,使用美图秀秀、Photoshop等工具,或只是截图工具中直接划线、添加文字就可以快速实现。但当需要处理的是批量数据,手工方式就不适合了。

二 一个简单的想法

因为最终是要写入word,所以暂时考虑还是使用XWPFRun.addPicture方法在单元格插入图片。但下一步,我们要在执行插入前,对图片做完所需的处理动作。这里可以考虑ImageIO 和 Graphics,这两个Java中的图片图形处理工具类来实现了。

三 Graphics

3.1 简介

java.awt.Graphics是一个抽象类,根据源码中的文档描述,

Graphics类是所有图形上下文的抽象基类,允许应用程序绘制在各种设备上实现的组件以及屏幕外图像上。

Graphics的使用已经被很多人整理过,所以本篇就不再赘述。可以参考文章:Java Graphics类的绘图方法了解完整的使用方法。这里只抽取所需的方法介绍,并给出示例。

3.2 矩形绘制

在图片中绘制一个矩形,来代表框选区域,通过配合颜色选择(红橙黄),可以起到标示作用。矩形区域,我们按照从左到右的方向,可以简单用起点横坐标:x,起点纵坐标:y,以及宽度width+高度height来确定。当然,其实宽度和高度也可以替换终点横坐标 和 终点纵坐标来标示。

在Graphics中绘制矩形的方法:

public void drawRect(int x, int y, int width, int height)

3.3 多边形绘制

矩形只有四个点,显示中可能需要绘制复杂的多边形,那么上述方法就无法满足了。所以Graphics提供了一个更为通用的绘制多边形方法:

public abstract void drawPolygon(int xPoints[], int yPoints[], int nPoints);

参数的xPoints,yPoints,nPoints分别代表横坐标数组,纵坐标数组,线段个数;xPoints和yPoints大小必须相同。

这个方法会绘制由 nPoint 个线段定义的多边形,其中前 nPoint - 1 个线段是 1 ≤ i ≤ 时从 (xPoints[i - 1], yPoints[i - 1]) 到 (xPoints[i], yPoints[i]) 的线段。如果最后一个点和第一个点不同,则图形会通过在这两点间绘制一条线段来自动闭合。

除了直接输入坐标,也可以通过传入定义的Polygon类来进行封装,使用drawPolygon(Polygon p)实现绘制:

public void drawPolygon(Polygon p) { drawPolygon(p.xpoints, p.ypoints, p.npoints); }

3.4 实现示例

/** * 添加多边形 * @param imgFile * @return * @throws Exception */ public static BufferedImage addPolygon(String imgFile) throws Exception { BufferedImage image = ImageIO.read(new File(imgFile)); Graphics g = image.getGraphics(); int px2[]={100,480,470,480,240,100,110,100}; int py2[]={155,175,185,395,315,185,175,155}; g.setColor(Color.red); g.drawPolygon(px2,py2,8); return image; }




四 处理结果写入word

4.1 参数转换

接下来回到另一个关键问题:图片处理结果怎样写入word?先回顾一下上一篇的内容:

run.addPicture 接收的参数依次为:图片的 InputStream 流,图片类型,图片名称(非文件名),图片宽度、图片高度。通过这个方法,我们就可以把图片插入到指定的表格中,并设置图片的宽高属性。

对于图片输入,addPicture要求的参数是InputStream,而我们上面的图片处理结果,是BufferedImage。显然是无法直接插入到word的Cell中的。那么该怎么办?两种方法,要么另寻出路,看是否有图片处理完成的结果是InputStream;要么就是想办法把BufferedImage转成InputStream。

写到这里,我想起了一句话:『当手里有锤子,看什么都像钉子』。这句话本来是说容易养成思维定势,但在现实中,如果我们看到的东西限制在某些场景,例如金属制品,那么当我们的需求是把其他东西固定住时,考虑用手中的锤子把已有的材料加工成钉子来达到目的也未尝不可,尽管可能不是最佳的方法。当然,这种尝试并不是盲目的,而是在目标明确,且对材料和手中的工具有一定了解的情况下。

话说回来,BufferedImage本身也是一种流的结构,那么就存在着转为ImputStream的可能。

4.2 代码实现与示例

将BufferedImage转换为InputStream,亲测可用 这篇文章就给出了解决方法,所以采用了这段代码加以实现:

/** * 将BufferedImage转换为InputStream * @param image * @return */ public static InputStream bufferedImageToInputStream(BufferedImage image){ ByteArrayOutputStream os = new ByteArrayOutputStream(); try { ImageIO.write(image, "png", os); InputStream input = new ByteArrayInputStream(os.toByteArray()); return input; } catch (IOException e) { e.printStackTrace(); } return null; }

通过这种方式,结合Graphics对图片的处理,得到的word导出结果如下图所示:

五 总结

在日常开发中,经常会遇到各种问题。排除不合理需求,解决困难的技术问题,保证项目的按时保质完成也是我们作为开发者的几项重要职责。在解决问题的过程中,明确目标、识别关键问题、搜索能力、问题拆解和解决能力都很重要。所谓的『经验』,就是在日常大大小小的考验过后沉淀下来的各项能力,而不只是经历。这点需要特别注意,与大家共勉。