类名 | 描述 |
---|---|
ImageIO | 一个类包含静态方便定位方法 ImageReader s和 ImageWriter s,并进行简单的编码和解码。 |
BufferedImage | BufferedImage 类描述了一个 Image 与访问图像数据缓冲区。一个 BufferedImage 是由一个 ColorModel 和 Raster 图像数据。在 Raster 的 SampleModel 数量和类型的乐队必须匹配的数量和类型的 ColorModel 要求代表它的颜色和alpha分量。所有的 BufferedImage 对象的左上角坐标(0, 0)。任何 Raster 用来构建一个 BufferedImage 必须有X = 0和迷你= 0。 这类依赖于数据的读取和Raster 设置方法,并对ColorModel 颜色表征方法。 |
Graphics2D | 提供强大的绘图能力。Graphic2D类扩展了Graphic类提供几何更复杂的控制系统的坐标变换,色彩管理和文本布局。 |
AlphaComposite | 实现基本的alpha合成相结合的源和目标的颜色来实现融合,图形和图像的透明度的影响规律 |
ImageIO的方法
Modifier and Type | Method and Description |
---|---|
static ImageInputStream |
createImageInputStream(Object input) 返回一个 ImageInputStream 将从给定的输入 Object 。 |
static ImageOutputStream |
createImageOutputStream(Object output) 返回一个 ImageOutputStream ,将其输出到了 Object 。 |
static File |
getCacheDirectory() 返回由 setCacheDirectory 的当前值,或 null 如果没有显式设置了。 |
static ImageReader |
getImageReader(ImageWriter writer) 返回一个 ImageReader corresponding到给定的 ImageWriter ,如果有一个,或 null 如果插件,这 ImageWriter 不指定相应的 ImageReader ,或者给 ImageWriter 没有注册。 |
static Iterator |
getImageReaders(Object input) 返回一个包含所有当前注册 ImageReader s Iterator 声称能提供 Object 解码,通常 ImageInputStream 。 |
static Iterator |
getImageReadersByFormatName(String formatName) 返回一个包含所有当前注册 Iterator ImageReader s声称能够解码的命名格式。 |
static Iterator |
getImageReadersByMIMEType(String MIMEType) 返回一个包含所有当前注册 Iterator ImageReader s声称能够解码文件与给定的MIME类型。 |
static Iterator |
getImageReadersBySuffix(String fileSuffix) 返回一个包含所有当前注册 Iterator ImageReader s声称能够解码文件的后缀。 |
static Iterator |
getImageTranscoders(ImageReader reader, ImageWriter writer) 返回一个包含所有当前注册 ImageTranscoder s Iterator 声称能将给定的 ImageReader 和 ImageWriter 元数据之间。 |
static ImageWriter |
getImageWriter(ImageReader reader) 返回一个 ImageWriter corresponding到给定的 ImageReader ,如果有一个,或 null 如果插件,这 ImageReader 不指定相应的 ImageWriter ,或者给 ImageReader 没有注册。 |
static Iterator |
getImageWriters(ImageTypeSpecifier type, String formatName) 返回一个包含所有当前注册 Iterator ImageWriter s声称能够编码的图像(使用一个给定的布局 ImageTypeSpecifier 指定)在给定的格式。 |
static Iterator |
getImageWritersByFormatName(String formatName) 返回一个包含所有当前注册 Iterator ImageWriter s声称能够编码的命名格式。 |
static Iterator |
getImageWritersByMIMEType(String MIMEType) 返回一个包含所有当前注册 ImageWriter s Iterator 声称能与给定的MIME类型文件的编码。 |
static Iterator |
getImageWritersBySuffix(String fileSuffix) 返回一个包含所有当前注册 ImageWriter s Iterator 声称能与给定的后缀文件编码。 |
static String[] |
getReaderFileSuffixes() 返回 String s列出所有与格式的当前注册读者理解相关的文件后缀数组。 |
static String[] |
getReaderFormatNames() 返回 String s列出所有的非正式格式名称注册读者理解数组的当前设置。 |
static String[] |
getReaderMIMETypes() 返回 String s列出所有的MIME类型注册读者了解当前设置的数组。 |
static boolean |
getUseCache() 返回由 setUseCache 的当前值,或 true 如果没有显式设置了。 |
static String[] |
getWriterFileSuffixes() 返回 String s列出所有的格式由注册作家当前理解的关联文件后缀数组。 |
static String[] |
getWriterFormatNames() 返回 String s列出所有的非正式格式名称注册作家当前理解数组。 |
static String[] |
getWriterMIMETypes() 返回 String s列出所有的MIME类型注册作家当前理解数组。 |
static BufferedImage |
read(File input) 返回一个 BufferedImage 作为一个 ImageReader 自动选择从当前注册提供 File 解码结果。 |
static BufferedImage |
read(ImageInputStream stream) 返回一个 BufferedImage 作为一个 ImageReader 自动选择从当前注册提供 ImageInputStream 解码结果。 |
static BufferedImage |
read(InputStream input) 返回一个 BufferedImage 作为一个 ImageReader 自动选择从当前注册提供 InputStream 解码结果。 |
static BufferedImage |
read(URL input) 返回一个 BufferedImage 作为一个 ImageReader 自动选择从当前注册提供 URL 解码结果。 |
static void |
scanForPlugins() 在应用程序的类路径加载插件扫描,服务提供程序类,并注册一个服务提供商,例如每一个发现与 IIORegistry 。 |
static void |
setCacheDirectory(File cacheDirectory) 设置要创建缓存文件的目录。 |
static void |
setUseCache(boolean useCache) 设置一个指示是否基于磁盘的缓存文件应该创造 ImageInputStream s和 ImageOutputStream s时使用的国旗。 |
static boolean |
write(RenderedImage im, String formatName, File output) 写一个图像使用任意 ImageWriter 支持特定格式的一 File 。 |
static boolean |
write(RenderedImage im, String formatName, ImageOutputStream output) 写一个图像使用任意 ImageWriter 支持给格式的 ImageOutputStream 。 |
static boolean |
write(RenderedImage im, String formatName, OutputStream output) 写一个图像使用任意 ImageWriter 支持给格式的 OutputStream 。 |
BufferedImage方法
Modifier and Type | Field and Description |
---|---|
static int |
TYPE_3BYTE_BGR 代表8位RGB分量图像,对应一个Windows风格BGR颜色模型)与蓝色、绿色、红色3个字节存储。 |
static int |
TYPE_4BYTE_ABGR 代表8位RGBA颜色成分的蓝色、绿色和红色的图像,存储在3字节和1字节的α。 |
static int |
TYPE_4BYTE_ABGR_PRE 代表8位RGBA颜色成分的蓝色、绿色和红色的图像,存储在3字节和1字节的α。 |
static int |
TYPE_BYTE_BINARY 表示一个不透明的字节填充的1,2,或4位图像。 |
static int |
TYPE_BYTE_GRAY 表示一个无符号字节的灰度图像,无索引。 |
static int |
TYPE_BYTE_INDEXED 表示一个索引字节图像。 |
static int |
TYPE_CUSTOM 图像类型是不被识别的,所以它必须是一个自定义的图像。 |
static int |
TYPE_INT_ARGB 代表8位RGBA颜色组件包装成整数像素的图像。 |
static int |
TYPE_INT_ARGB_PRE 代表8位RGBA颜色组件包装成整数像素的图像。 |
static int |
TYPE_INT_BGR 代表8位RGB分量图像,对应于Windows或Solaris式BGR颜色模型,用蓝色、绿色和红色包装成整数像素。 |
static int |
TYPE_INT_RGB 代表8位RGB分量包装成整数像素的图像。 |
static int |
TYPE_USHORT_555_RGB 代表5-5-5 RGB分量图像(五位红、五位绿色,五位蓝色)没有α。 |
static int |
TYPE_USHORT_565_RGB 代表5-6-5 RGB分量图像(五位红,六位绿色,五位蓝色)没有α。 |
static int |
TYPE_USHORT_GRAY 表示一个无符号的短灰度图像,非索引的。 |
Graphics2D
Graphic2D类提供强大的绘图能力。Graphic2D类扩展了Graphic类提供几何更复杂的控制系统的坐标变换,色彩管理和文本布局。
Java文档
坐标空间
所有坐标传递给 Graphics2D对象在一个独立于设备的坐标系统称为用户空间的规定,这是由应用程序使用。的 Graphics2D对象包含一个 AffineTransform对象作为其渲染状态的一部分,定义了如何转换坐标从用户空间到设备空间依赖于设备坐标。
在设备空间中的坐标通常是指单独的设备像素,并对准这些像素之间的无限薄的差距。一些Graphics2D对象可以用来捕捉渲染操作存储成一个图形文件对以后未知的物理分辨率的具体设备的播放。由于分辨率可能不知道,当绘制操作的Graphics2D Transform捕获,建立用户坐标系变换到一个虚拟的设备空间,接近目标设备预期的分辨率。进一步的转换可能需要被应用在播放时间,如果估计是不正确的。
有些作业的渲染属性的对象出现在装置的空间,但是所有的Graphics2D方法把用户空间坐标。
每一Graphics2D对象与目标定义在渲染发生相关。一个GraphicsConfiguration对象定义的渲染目标的特征,如像素格式和分辨率。相同的渲染目标是在一个Graphics2D对象的生活。
创建一个Graphics2D对象时的GraphicsConfiguration指定的Graphics2D目标default transform(一Component或Image)。这个默认变换将用户空间坐标系统映射到屏幕和打印机设备的坐标,这样的原点映射到设备的目标区域的左上角,增加x坐标延伸到右边,增加Y坐标向下延伸。默认的缩放变换是这些设备的接近72的DPI设置为身份,如屏幕设备。默认变换的缩放设置为每平方英寸约72个用户空间坐标,用于高分辨率设备,如打印机。图像缓冲区,默认的变换是Identity变换。
绘制过程
渲染过程可以分成四个阶段,由 Graphics2D渲染属性控制。渲染器可以优化这些步骤,通过缓存未来呼吁的结果,被倒塌的多个虚拟成一个单一的操作步骤,或通过识别各种属性作为常见的简单的情况,可以通过修改操作的其他部分消除。
渲染过程中的步骤是:
决定要渲染什么。
约束的绘制操作的当前Clip,Clip是由用户空间的一个Shape指定是用Graphics和Graphics2D各种剪辑手法的程序控制。这个空用户卡转化装置的空间由目前的Transform结合空夹持器,这是由Windows和设备程度的可见性定义。用户的夹具和装置夹的组合定义了空复合夹,这决定了最终的裁剪区域。用户剪辑不被渲染系统修改,以反映所得到的复合剪辑。
确定要渲染的颜色。
应用颜色到目的地绘制表面采用目前Composite属性在Graphics2D语境。
三种绘制作业,随着他们的每一个特定的渲染过程的细节是:
Shape operations
如果操作是一个draw(Shape)操作,然后createStrokedShape法在Graphics2D上下文的当前Stroke属性是用来构建一个新的Shape对象包含指定的Shape概述。
是的Shape从用户空间转换到设备空间使用当前Transform在Graphics2D语境。
该Shape轮廓用Shape的getPathIterator提取方法,它返回一个对象,PathIterator迭代的Shape沿边界。
如果Graphics2D对象不能处理的曲线段,PathIterator对象返回就可以打电话Shape交替getPathIterator方法,并将Shape。
在Graphics2D上下文的当前Paint查询一个PaintContext,指定颜色在设备空间渲染。
Text operations
以下步骤用于确定需要渲染的结果String符号集:
如果参数是一个String,然后在Graphics2D上下文的当前Font要求转换为Unicode字符在String为一组符号表示什么的基本布局和整形算法实现的字体。
如果参数是一个AttributedCharacterIterator,迭代器要求将自己一TextLayout使用嵌入式字体属性。实现更复杂的TextLayout字形布局算法进行Unicode双向布局自动调整为不同的写作方向,多种字体。
如果参数是一个GlyphVector,然后GlyphVector对象已经包含合适的字体特定的字形码为每个符号的位置明确的坐标。
目前Font查询获得的指示符号概述。这些大纲被视为在用户空间中的形状相对于每一个字形的位置,在步骤1中确定。
字符轮廓填充如上下Shape operations。
目前Paint查询一个PaintContext,指定颜色在设备空间渲染。
Image Operations
感兴趣区域是由原Image包围盒的定义。这个边界框在图像空间中指定的,这是Image对象的局部坐标系统。
如果一个AffineTransform传递的drawImage(Image, AffineTransform, ImageObserver),AffineTransform用于将包围盒从图像空间到用户空间。如果不提供AffineTransform,包围盒作为如果它已经在用户空间。
的源Image包围盒是从用户空间转化装置的空间使用当前Transform。请注意,转换的包围盒的结果并不一定会导致在设备空间中的矩形区域。
的Image对象决定什么颜色渲染,取样根据源到目的地的坐标映射指定的电流Transform和可选的图像变换。
默认的渲染属性
默认值为 Graphics2D渲染属性:
nullpaint
Component的颜色。
nullfont
该 Component的 Font。
nullstroke
方笔线宽1,没有浮华,斜切段连接和方形端盖。
nulltransform
对 Component的 GraphicsConfiguration的 getDefaultTransform。
nullcomposite
的 AlphaComposite.SRC_OVER规则。
nullclip
没有渲染 Clip,输出的是夹到 Component。
渲染的兼容性问题
JDK(TM)1.1绘制模型是基于像素化模型,指定坐标是无限薄,躺在之间的像素。使用一一个像素宽的笔,填充的像素的下方,并在路径上的锚点的右侧进行绘图操作。JDK 1.1绘制模型与大多数平台的渲染器,需要解决整数坐标离散的笔,必须完全落在指定的像素数现有类的能力相一致。
java 2D(TM)(java(TM)2平台)API支持抗锯齿渲染。一个宽度为一个像素的笔不需要完全落在像素N,而不是像素n + 1。笔可以部分落在两个像素上。它是没有必要选择一个广泛的笔的偏置方向,因为沿笔遍历边缘发生的混合,使笔的子像素位置的用户可见。另一方面,当抗锯齿设置KEY_ANTIALIASING暗示关键的VALUE_ANTIALIAS_OFF提示值关闭,渲染器可能需要申请一个偏置来确定哪些像素修改当笔跨界像素边界,如当它是画在设备空间的整数坐标。虽然一个抗锯齿渲染能力使渲染的模型指定为钢笔的偏见不再是必要的,它是理想的抗锯齿和非抗锯齿渲染执行同样的常见情况绘制一个像素宽的水平和垂直线的屏幕上。确保打开抗锯齿设置KEY_ANTIALIASING提示键VALUE_ANTIALIAS_ON不会引起这些线突然变得宽两倍,一半是不透明的,是最为理想的模型指定一个这样的线路路径使他们完全覆盖一组特定的像素来帮助他们增加脆度。
java JDK 1.1绘制2D API保持行为的相容性,使得传统业务和现有的渲染行为不变的情况下,java 2D API。传统的方法是定义映射到一般draw和fill方法,这清楚地表明,如何Graphics2D延伸Graphics基于Stroke和Transform属性和渲染提示设置。定义执行相同的默认属性设置。例如,默认Stroke是BasicStroke宽度在1和没有闯劲、默认变换屏幕绘图是一个恒等变换。
以下两个规则提供可预测的渲染行为是否锯齿或锯齿被使用。
设备坐标系定义为设备像素之间,避免任何不一致的结果之间的锯齿和抗锯齿渲染。如果坐标被定义为在一个像素的中心,一些覆盖的形状,如一个矩形,只会被覆盖的一半。与处理、半覆盖的像素将被呈现在形状或外部形状。抗锯齿渲染,对塑造整个边缘像素会有一半。另一方面,由于坐标定义为像素之间,形状像一个矩形将没有半覆盖的像素,是否使用抗锯齿渲染。
线和路径抚摸使用BasicStroke对象可能是“标准化”提供了一致的渲染定位时,在各点上的冲和是否绘制锯齿或锯齿渲染。这种过程是由KEY_STROKE_CONTROL提示控制。没有指定确切的归一化算法,但这种正常化的目标是确保线条的呈现一致的外观无论怎样它们落在像素网格和促进反锯齿模式更坚实的水平线和垂直线使他们像他们的非抗锯齿的同行更密切。一个典型的归一化步骤可能促进抗锯齿线端点像素中心降低掺量或调整非抗锯齿线的亚像素定位使浮点线宽度轮偶数或奇数像素数相等的可能性。这个过程可以移动端点高达半个像素(通常是沿两个轴的正无穷大),以促进这些一致的结果。
一般传统方法的以下定义执行相同的默认属性设置下的以前指定的行为:
对于fill操作,包括fillRect,fillRoundRect,fillOval,fillArc,fillPolygon,和clearRect,fill现在可以被称为理想的Shape。例如,当填充矩形:
填充(新的矩形(X,Y,W,H));
叫做。
同样,对于绘制操作,包括drawLine,drawRect,drawRoundRect,drawOval,drawArc,drawPolyline,和drawPolygon,draw现在可以被称为理想的Shape。例如,当绘制一个矩形:
绘制(新的矩形(X,Y,W,H));
叫做。
在Graphics类,谓其行为在Graphics2D上下文的当前Stroke和Paint对象的drawLine和fillRect方法上实现了draw3DRect和fill3DRect方法。这类重写这些实现版本使用当前Color完全覆盖当前Paint和使用fillRect描述完全相同的行为,原有的方法无论当前Stroke设置。
的 Graphics类只定义 setColor方法控制颜色画。由于java 2D API扩展 Color对象实施新的 Paint接口,现有的 setColor方法现在是一个用于设置当前 Paint属性到一个 Color对象方便的方法。 setColor(c)相当于 setPaint(c)。
的Graphics类定义了两个方法控制颜色如何应用到目的地。
的setPaintMode方法是设置默认Composite方便的方法实现的,相当于setComposite(new AlphaComposite.SrcOver)。
的setXORMode(Color xorcolor)方法的实施作为一种方便的方法,设置了一个特殊的Composite对象,忽略了Alpha组件源颜色和目标颜色设置值:
dstpixel =(PixelOf(srccolor)^ PixelOf(xorcolor)^ dstpixel);
方法
Modifier and Type | Method and Description |
---|---|
abstract void |
addRenderingHints(Map,?> hints) 设置渲染算法的任意数量的首选项的值。 |
abstract void |
clip(Shape s) 与目前的 Clip 与指定的 Shape 室内设置产生的交叉 Clip 。 |
abstract void |
draw(Shape s) 下一 Shape 使用当前 Graphics2D 语境设置的轮廓。 |
void |
draw3DRect(int x, int y, int width, int height, boolean raised) 绘制指定矩形的三维突出显示的轮廓。 |
abstract void |
drawGlyphVector(GlyphVector g, float x, float y) 使文本的指定 GlyphVector 使用 Graphics2D 语境的渲染属性。 |
abstract void |
drawImage(BufferedImage img, BufferedImageOp op, int x, int y) 呈现 BufferedImage ,与 BufferedImageOp 过滤。 |
abstract boolean |
drawImage(Image img, AffineTransform xform, ImageObserver obs) 渲染一个图像,在绘制之前将图像空间的一个变换转换成用户空间。 |
abstract void |
drawRenderableImage(RenderableImage img, AffineTransform xform) 呈现 RenderableImage ,应用变换从图像空间到用户空间的画前。 |
abstract void |
drawRenderedImage(RenderedImage img, AffineTransform xform) 呈现 RenderedImage ,应用变换从图像空间到用户空间的画前。 |
abstract void |
drawString(AttributedCharacterIterator iterator, float x, float y) 使指定的迭代器将其属性按照 TextAttribute 类的规范文本。 |
abstract void |
drawString(AttributedCharacterIterator iterator, int x, int y) 使指定的迭代器将其属性按照 TextAttribute 类的规范文本。 |
abstract void |
drawString(String str, float x, float y) 将由指定的 String 指定的文本,使用当前的文本属性状态的 Graphics2D 语境。 |
abstract void |
drawString(String str, int x, int y) 使指定的 String 文本,使用当前的文本属性状态的 Graphics2D 语境。 |
abstract void |
fill(Shape s) 填补了一 Shape 使用的 Graphics2D 语境设置的内部。 |
void |
fill3DRect(int x, int y, int width, int height, boolean raised) 画一个充满当前颜色的三维高亮矩形。 |
abstract Color |
getBackground() 返回用于清除区域的背景色。 |
abstract Composite |
getComposite() 在返回的 Graphics2D 上下文的当前 Composite 。 |
abstract GraphicsConfiguration |
getDeviceConfiguration() 返回与此相关的设备配置 Graphics2D 。 |
abstract FontRenderContext |
getFontRenderContext() 在这 Graphics2D 上下文得到的 Font 渲染上下文。 |
abstract Paint |
getPaint() 返回的 Graphics2D 上下文的当前 Paint 。 |
abstract Object |
getRenderingHint(RenderingHints.Key hintKey) 返回一个单独的渲染算法的偏好值。 |
abstract RenderingHints |
getRenderingHints() 获取渲染算法的首选项。 |
abstract Stroke |
getStroke() 在返回的 Graphics2D 上下文的当前 Stroke 。 |
abstract AffineTransform |
getTransform() 返回一个在 Graphics2D 上下文的当前 Transform 。 |
abstract boolean |
hit(Rectangle rect, Shape s, boolean onStroke) 检查是否 Shape 相交的指定 Rectangle ,这是设备空间。 |
abstract void |
rotate(double theta) 将当前 Graphics2D Transform 与旋转变换。 |
abstract void |
rotate(double theta, double x, double y) 将当前 Graphics2D Transform 与翻译旋转变换。 |
abstract void |
scale(double sx, double sy) 将当前 Graphics2D Transform 与尺度转换以后呈现大小按指定的比例因子缩放相对于以前。 |
abstract void |
setBackground(Color color) 设置为 Graphics2D 上下文的背景颜色。 |
abstract void |
setComposite(Composite comp) 设置为 Graphics2D 语境 Composite 。 |
abstract void |
setPaint(Paint paint) 设置为 Graphics2D 语境 Paint 属性。 |
abstract void |
setRenderingHint(RenderingHints.Key hintKey, Object hintValue) 设置一个单独的渲染算法的偏好值。 |
abstract void |
setRenderingHints(Map,?> hints) 取代所有偏好值与指定的 hints 渲染算法。 |
abstract void |
setStroke(Stroke s) 设置为 Graphics2D 语境 Stroke 。 |
abstract void |
setTransform(AffineTransform Tx) 覆盖在 Graphics2D 语境的变换。 |
abstract void |
shear(double shx, double shy) 将当前 Graphics2D Transform 与剪切变换。 |
abstract void |
transform(AffineTransform Tx) 在这 Graphics2D 组成的 Transform AffineTransform 对象根据规则上指定的首次应用。 |
abstract void |
translate(double tx, double ty) 将当前 Graphics2D Transform 与平移变换。 |
abstract void |
translate(int x, int y) 翻译语境的 Graphics2D 起源点(nullx, nully)在当前坐标系统。 |
读取图片
BufferedImage read = ImageIO.read(new FileInputStream(imgPath));
保存图片
//保存图片
File file = new File("D:\\image.jpg");
ImageIO.write( new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB), "jpg", file);
将一幅图片输入输出:
public static void main(String[] args) {
try {
//将图片读入内存
String imgPath = "D:\\image.jpg";
BufferedImage read = ImageIO.read(new FileInputStream(imgPath));
//保存图片
File file = new File("D:\\imagesave.jpg");
ImageIO.write( new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB), "jpg", file);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
*
* @param width e.g. 20
* @param height e.g. 40
* @param imageType e.g. BufferedImage.TYPE_BYTE_GRAY
* @return
*/
public BufferedImage createdBufferedImage(Integer width,Integer height,Integer imageType) {
//指定宽高、创建带灰色的对象:
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
//创建一个不带透明色的对象
BufferedImage bufferedImage1 = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
//创建一个带透明色的对象
BufferedImage bufferedImage2 = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
return null;
}
/**
* BufferedImage 功能介绍
*/
public static void imageIOApi() {
try {
BufferedImage bufferedImage = ImageIO.read(new File("D:/watermark.jpg"));
//获取图片的宽高
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
//图片裁剪
BufferedImage subimage = bufferedImage.getSubimage(0, 0, 10, 10);
//创建画笔对象
Graphics2D graphics = bufferedImage.createGraphics();
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void drawImage() {
try {
//指定宽高、创建带灰色的对象:
BufferedImage bufferedImage = new BufferedImage(200, 200, BufferedImage.TYPE_USHORT_555_RGB);
Graphics2D graphics = bufferedImage.createGraphics();
graphics.drawImage(bufferedImage,200,200,null);
ImageIO.write(bufferedImage, "jpg", new File("D:\\test.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void setAlphaComposite() {
try {
//源文件
BufferedImage original = ImageIO.read(new FileInputStream("D:\\original.jpg"));
//黑框
BufferedImage bufferedImage = new BufferedImage(200, 200, BufferedImage.TYPE_INT_RGB);
//源文件上创建画笔 Graphic2D
Graphics2D graphics = original.createGraphics();
//设置透明度
graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.3f));
graphics.drawImage(bufferedImage,200,200,null);
ImageIO.write(original, "jpg", new File("D:\\test.jpg"));
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void rotate() {
try {
BufferedImage read = ImageIO.read(new FileInputStream("D:\\original.jpg"));
BufferedImage bufferedImage = new BufferedImage(200, 200, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = read.createGraphics();
graphics2D.rotate(50d);
graphics2D.drawImage(bufferedImage, 200, 200, null);
ImageIO.write(read, "jpg", new File("D:\\test.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void rotate() {
try {
BufferedImage read = ImageIO.read(new FileInputStream("D:\\original.jpg"));
BufferedImage bufferedImage = new BufferedImage(200, 200, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = read.createGraphics();
graphics2D.rotate(50d);
//控制图片位置
graphics2D.drawImage(bufferedImage, 10, 10, null);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 在(3,3)与(50,50)之间画一条直线
*/
@Test
public void graphics2DApi() {
try {
BufferedImage read = ImageIO.read(new FileInputStream("D:\\image.jpg"));
Graphics2D graphics = read.createGraphics();
graphics.setColor(Color.red);
//在窗口画一条直线
graphics.drawLine(3,3,50,50);
ImageIO.write(read, "jpg",new File("D:\\line.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void fontMetrics() {
try {
String s1 = "Hello,Java World!";
BufferedImage read = ImageIO.read(new FileInputStream("D:\\test.jpg"));
Graphics2D graphics = read.createGraphics();
graphics.setColor(Color.red);
graphics.setBackground(new Color(0,250,0));
Font font = new Font("Arial", Font.BOLD, 18);
graphics.setFont(font);
FontMetrics fontMetrics = graphics.getFontMetrics(font);
int height = fontMetrics.getHeight();
int widths = fontMetrics.stringWidth(s1);
int posx = 50;
int posy = 50;
graphics.drawString(s1, posx, posy);
graphics.drawString("I Will come in.",posx+widths,posy+height);
ImageIO.write(read, "jpg", new File("D:\\test1.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
}
以下是PDFBox的显着特征 -
以下是PDFBox的应用 -
以下是PDFBox的四个主要组成部分 -
通过maven方式安装
<dependencies>
<dependency>
<groupId>org.apache.pdfboxgroupId>
<artifactId>pdfboxartifactId>
<version>2.0.1version>
dependency>
<dependency>
<groupId>org.apache.pdfboxgroupId>
<artifactId>fontboxartifactId>
<version>2.0.0version>
dependency>
<dependency>
<groupId>org.apache.pdfboxgroupId>
<artifactId>jempboxartifactId>
<version>1.8.11version>
dependency>
<dependency>
<groupId>org.apache.pdfboxgroupId>
<artifactId>xmpboxartifactId>
<version>2.0.0version>
dependency>
<dependency>
<groupId>org.apache.pdfboxgroupId>
<artifactId>preflightartifactId>
<version>2.0.0version>
dependency>
<dependency>
<groupId>org.apache.pdfboxgroupId>
<artifactId>pdfbox-toolsartifactId>
<version>2.0.0version>
dependency>
dependencies>
可以通过实例化PDFDocument类来创建空PDF文档。可以使用Save()方法保存所需的位置。
代码
/**
* 创建空PDF文档
*/
@Test
public void pdfBox() {
try {
//第一步:创建一个空文档
PDDocument document = new PDDocument();
//第二步:保存文档
document.save("D:\\test.pdf");
//第三步:关闭文档
document.close();
} catch (IOException e) {
e.printStackTrace();
}
}
可以通过实例化PDPage类创建一个空页面,并使用PDDocument类的**addPage()**方法将其添加到PDF文档中。
代码
/**
* 添加页面到PDF文档
*/
@Test
public void addingPages() {
try {
//第一步:打开一个文档
PDDocument document = PDDocument.load(new FileInputStream("D:\\test.pdf"));
for (int i = 0; i < 10; i++) {
//第二步:创建空白页面
PDPage pdPage = new PDPage();
//第三步:向文档添加页面
document.addPage(pdPage);
}
//第四步:保存文档
document.save("D:\\test2.pdf");
document.close();
} catch (IOException e) {
e.printStackTrace();
}
}
PDDocument类的**load()**方法用于加载现有PDF文档。 按照以下步骤加载现有PDF文档。
代码
@Test
public void loadDoc() {
try {
//加载一个已存在的pdf文档
PDDocument document = PDDocument.load(new File("D:\\test2.pdf"));
//向文档添加一个新的页面
document.addPage(new PDPage());
//保存文档
document.save("D:\\test3.pdf");
//关闭文档
document.close();
} catch (IOException e) {
e.printStackTrace();
}
}
您可以使用PDDocument类的**removePage()**方法从现有PDF文档中删除页面。
代码
@Test
public void removePage(){
try {
//加载一个现有文档
PDDocument document = PDDocument.load(new FileInputStream("D:\\test3.pdf"));
int numberOfPages = document.getNumberOfPages();
System.out.println("PDF pages :"+ numberOfPages);
//删除第二页, 索引为1
document.removePage(1);
System.out.println("After Delete, PDF Page:"+document.getNumberOfPages());
//保存文档
document.save(new FileOutputStream("D:\\test4.pdf"));
//关闭文档
document.close();
} catch (IOException e) {
e.printStackTrace();
}
}
可以分别使用PDImageXObject和PDPageContentStream类的**createFromFile()和drawImage()**方法将图像插入PDF文档。
代码
@Test
public void insertImage() {
try {
//加载现有文档
PDDocument document = PDDocument.load(new FileInputStream(new File("D:\\test.pdf")));
document.addPage(new PDPage());
//检索页面
PDPage page = document.getPage(0);
//创建PDImageXObject对象
PDImageXObject imageXObject = PDImageXObject.createFromFile("D:\\image.jpg", document);
//准备内容流
PDPageContentStream contentStream = new PDPageContentStream(document, page);
//在PDF文档中添加图像
contentStream.drawImage(imageXObject,70,250);
//关闭contentStream
contentStream.close();
//保存文件
document.save("D:\\inertImage.pdf");
//关闭文档
document.close();
} catch (Exception e) {
e.printStackTrace();
}
}
通过PDExtendedGraphicsState的setNonStrokingAlphaConstant设置透明度
代码
@Test
public void alphaSource() {
try {
PDDocument document = new PDDocument();
document.addPage(new PDPage());
//检索页面
PDPage page = document.getPage(0);
//创建PDImageXObject对象
PDImageXObject imageXObject = PDImageXObject.createFromFile("D:\\爱莎1.jpg", document);
//准备内容流
PDPageContentStream contentStream = new PDPageContentStream(document, page);
PDExtendedGraphicsState pdExtendedGraphicsState = new PDExtendedGraphicsState();
//透明度
pdExtendedGraphicsState.setNonStrokingAlphaConstant(0.5f);
pdExtendedGraphicsState.setAlphaSourceFlag(true);
pdExtendedGraphicsState.getCOSObject().setItem(COSName.BM,COSName.MULTIPLY);
contentStream.setGraphicsStateParameters(pdExtendedGraphicsState);
//在PDF文档中添加图像
contentStream.drawImage(imageXObject,70,250);
//关闭contentStream
contentStream.close();
//保存文件
document.save("D:\\inertImage.pdf");
//关闭文档
document.close();
} catch (IOException e) {
e.printStackTrace();
}
}
通过Matrix对象,document.transform(maxtrix)来旋转图片
代码
@Test
public void rotate() {
try {
PDDocument document = new PDDocument();
document.addPage(new PDPage());
//检索页面
PDPage page = document.getPage(0);
//创建PDImageXObject对象
PDImageXObject imageXObject = PDImageXObject.createFromFile("D:\\爱莎1.jpg", document);
//准备内容流
PDPageContentStream contentStream = new PDPageContentStream(document, page);
PDExtendedGraphicsState pdExtendedGraphicsState = new PDExtendedGraphicsState();
//透明度
pdExtendedGraphicsState.setNonStrokingAlphaConstant(0.5f);
pdExtendedGraphicsState.setAlphaSourceFlag(true);
pdExtendedGraphicsState.getCOSObject().setItem(COSName.BM,COSName.MULTIPLY);
contentStream.setGraphicsStateParameters(pdExtendedGraphicsState);
//旋转
Matrix matrix = new Matrix();
matrix.rotate(Math.toRadians(50));
contentStream.transform(matrix);
//在PDF文档中添加图像
contentStream.drawImage(imageXObject,0,0);
//关闭contentStream
contentStream.close();
//保存文件
document.save("D:\\inertImage.pdf");
//关闭文档
document.close();
} catch (IOException e) {
e.printStackTrace();
}
}
通过contentStream.drawImage(image,100,200)
代码
@Test
public void insertImage() {
try {
//加载现有文档
PDDocument document = PDDocument.load(new FileInputStream(new File("D:\\test.pdf")));
document.addPage(new PDPage());
//检索页面
PDPage page = document.getPage(0);
//创建PDImageXObject对象
PDImageXObject imageXObject = PDImageXObject.createFromFile("D:\\爱莎1.jpg", document);
//准备内容流
PDPageContentStream contentStream = new PDPageContentStream(document, page);
//在PDF文档中添加图像
contentStream.drawImage(imageXObject,70,250);
//关闭contentStream
contentStream.close();
//保存文件
document.save("D:\\inertImage.pdf");
//关闭文档
document.close();
} catch (Exception e) {
e.printStackTrace();
}
}
maven的pom
先要配置repository
<repositories>
<repository>
<id>com.e-iceblueid>
<name>e-icebluename>
<url> https://repo.e-iceblue.com/nexus/content/groups/public/url>
repository>
repositories>
pom的dependency
<dependency>
<groupId>e-icebluegroupId>
<artifactId>spire.docartifactId>
<version>4.12.1version>
dependency>
Spire.Doc for Java提供的 Document类 可用于新建Word文档或加载现有的Word文件进行编辑。Word内容结构由Section节和Paragraph段落组成,向文档中添加节和段落时,分别使用 Document.addSetion() 方法和 Section.addParagraph() 方法。本次代码中将演示如何创建包含多个段落的Word文档。
下表中列出了代码中使用到的核心类:
类 | 解释 |
---|---|
Document | 表示文档模型 |
Section | 表示文档模型中的节 |
Paragraph | 表示文档模型中的段落 |
ParagraphStyle | 表示文档中的段落样式,如段落的字体格式、对齐方式、段首缩进以及段落间距等 |
import com.spire.doc.*;
import com.spire.doc.documents.HorizontalAlignment;
import com.spire.doc.documents.Paragraph;
import com.spire.doc.documents.ParagraphStyle;
import java.awt.*;
public class CreateWordDocument {
public static void main(String[] args){
//创建Word文档
Document document = new Document();
//添加一个section
Section section = document.addSection();
//添加三个段落至section
Paragraph para1 = section.addParagraph();
para1.appendText("滕王阁序");
Paragraph para2 = section.addParagraph();
para2.appendText("豫章故郡,洪都新府。星分翼轸,地接衡庐。襟三江而带五湖,控蛮荆而引瓯越。"+
"物华天宝,龙光射牛斗之墟;人杰地灵,徐孺下陈蕃之榻。雄州雾列,俊采星驰。台隍枕夷夏之交,宾主尽东南之美。"+
"都督阎公之雅望,棨戟遥临;宇文新州之懿范,襜帷暂驻。十旬休假,胜友如云;千里逢迎,高朋满座。"+
"腾蛟起凤,孟学士之词宗;紫电青霜,王将军之武库。家君作宰,路出名区;童子何知,躬逢胜饯。");
Paragraph para3 = section.addParagraph();
para3.appendText("时维九月,序属三秋。潦水尽而寒潭清,烟光凝而暮山紫。俨骖騑于上路,访风景于崇阿;临帝子之长洲,得天人之旧馆。"+
"层峦耸翠,上出重霄;飞阁流丹,下临无地。鹤汀凫渚,穷岛屿之萦回;桂殿兰宫,即冈峦之体势。");
//将第一段作为标题,设置标题格式
ParagraphStyle style1 = new ParagraphStyle(document);
style1.setName("titleStyle");
style1.getCharacterFormat().setBold(true);
style1.getCharacterFormat().setTextColor(Color.BLUE);
style1.getCharacterFormat().setFontName("宋体");
style1.getCharacterFormat().setFontSize(12f);
document.getStyles().add(style1);
para1.applyStyle("titleStyle");
//设置其余两个段落的格式
ParagraphStyle style2 = new ParagraphStyle(document);
style2.setName("paraStyle");
style2.getCharacterFormat().setFontName("宋体");
style2.getCharacterFormat().setFontSize(11f);
document.getStyles().add(style2);
para2.applyStyle("paraStyle");
para3.applyStyle("paraStyle");
//设置第一个段落的对齐方式
para1.getFormat().setHorizontalAlignment(HorizontalAlignment.Center);
//设置第二段和第三段的段首缩进
para2.getFormat().setFirstLineIndent(25f);
para3.getFormat().setFirstLineIndent(25f);
//设置第一段和第二段的段后间距
para1.getFormat().setAfterSpacing(15f);
para2.getFormat().setAfterSpacing(10f);
//保存文档
document.saveToFile("Output.docx", FileFormat.Docx);
}
}
加载文档
Document document = new Document();
document.loadFromFile("D:\\text.docx");
@Test
public void insertTextWaterMark() {
Document document = new Document();
document.loadFromFile("D:\\text.docx");
//开始插入水印
TextWatermark textWatermark = new TextWatermark();
textWatermark.setText("内部使用");
textWatermark.setFontSize(40);
textWatermark.setColor(Color.RED);
textWatermark.setLayout(WatermarkLayout.Diagonal);
document.getSections().get(0).getDocument().setWatermark(textWatermark);
//保存文件
document.saveToFile("D:\\text1.docx");
}
@Test
public void addImageWatermark() throws IOException {
//加载Word文档
Document document = new Document();
document.loadFromFile("D:\\text.docx");
//创建PictureWatermark实例
PictureWatermark picture = new PictureWatermark();
ClassPathResource classPathResource = new ClassPathResource(WATER_MARK_PATH);
//设置水印图片属性
picture.setPicture(new FileInputStream(classPathResource.getFile()));
picture.setScaling(150);
picture.isWashout(false);
//添加水印图片到文档
document.setWatermark(picture);
//保存结果文件
document.saveToFile("D:\\text1.docx", FileFormat.Docx );
}
ByteArrayInputStream swapStream = new ByteArrayInputStream(spire.toByteArray());
XWPFDocument document1 = new XWPFDocument(swapStream);
ByteArrayInputStream swapStream = new ByteArrayInputStream(spire.toByteArray());
XWPFDocument document1 = new XWPFDocument(swapStream);
document1.removeBodyElement(0);
document1.write(outputStream);
利用两层循环加水印,对比水印图片宽高和原图宽高。
@Component
public class ImageWaterMarker implements MyWaterMarker {
private static final int HEIGHT_STEP = 100;
private static final int WIDTH_STEP = 80;
private static final int ROTATE =150;
private static final String WATER_MARK_PATH = "watermark/watermark.png";
@Override
public String getFileType(String mimeType) {
return MimeTypeFileType.IMAGE.findFileType(mimeType);
}
@Override
public Boolean watermark(InputStream inputStream, OutputStream outputStream, String mimeType) {
try {
BufferedImage original = ImageIO.read(inputStream);
String fileType = getFileType(mimeType);
ClassPathResource classPathResource = new ClassPathResource(WATER_MARK_PATH);
//获取水印图片
BufferedImage markImage = ImageIO.read(classPathResource.getInputStream());
// determine image type and handle correct transparency
Graphics2D g2d = original.createGraphics();
//透明度
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.7f));
//角度
g2d.rotate(ROTATE);
//获取整个图片高度
int canvasHeight = original.getHeight();
//获取整个图片宽度
int canvasWidth = original.getWidth();
//水印高度
int markHeight = markImage.getHeight();
//水印宽度
int markWidth = markImage.getWidth();
for(int i=-canvasHeight;i<canvasWidth+canvasHeight;i=i+markWidth+WIDTH_STEP){
for(int j=-canvasWidth;j<canvasHeight+canvasWidth;j=j+markHeight+HEIGHT_STEP){
g2d.drawImage(markImage,i,j,markImage.getWidth(),markImage.getHeight(),null);
}
}
g2d.dispose();
ImageIO.write(original, fileType, outputStream);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
}
由于项目中针对流来操作加水印。
目前主流的项目部署方式时jar包形式,在jar包中获取文件,只能通过流的形式【此处是获取水印图片】
ClassPathResource classPathResource = new ClassPathResource(WATER_MARK_PATH);
classPathResource.getInputStream();
pdfbox没有提供以流的形式加载
重写了以流的形式生成PDImageXObject
public static PDImageXObject createFromFileByExtension(InputStream inputStream,String filename, PDDocument doc) throws IOException
{
int dot = filename.lastIndexOf('.');
if (dot == -1)
{
throw new IllegalArgumentException("Image type not supported: " + filename);
}
String ext = filename.substring(dot + 1).toLowerCase();
if ("jpg".equals(ext) || "jpeg".equals(ext))
{
PDImageXObject imageXObject = JPEGFactory.createFromStream(doc, inputStream);
inputStream.close();
return imageXObject;
}
if ("gif".equals(ext) || "bmp".equals(ext) || "png".equals(ext))
{
BufferedImage bim = ImageIO.read(inputStream);
return LosslessFactory.createFromImage(doc, bim);
}
throw new IllegalArgumentException("Image type not supported: " + filename);
}
最终代码
@Component
public class PdfWaterMarker implements MyWaterMarker {
private static final int HEIGHT_STEP = 100;
private static final int WIDTH_STEP = 80;
private static final double THETA = 30;
private static final String WATER_MARK_PATH = "watermark/watermark.png";
@Override
public String getFileType(String mimeType) {
return MimeTypeFileType.PDF.findFileType(mimeType);
}
@Override
public Boolean watermark(InputStream inputStream, OutputStream outputStream, String mimeType) {
try {
//打开pdf文件
PDDocument doc = PDDocument.load(inputStream);
doc.setAllSecurityToBeRemoved(true);
//遍历pdf所有页
for (PDPage page : doc.getPages()) {
ClassPathResource classPathResource = new ClassPathResource(WATER_MARK_PATH);
//创建PDImageXObject对象
PDImageXObject imageXObject = createFromFileByExtension(classPathResource.getInputStream(),classPathResource.getFilename(),doc);
PDPageContentStream cs = new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.APPEND, true, true);
PDExtendedGraphicsState pdExtGfxState = new PDExtendedGraphicsState();
// 水印透明度
pdExtGfxState.setNonStrokingAlphaConstant(0.8f);
pdExtGfxState.setAlphaSourceFlag(true);
pdExtGfxState.getCOSObject().setItem(COSName.BM, COSName.MULTIPLY);
cs.setGraphicsStateParameters(pdExtGfxState);
for(int y= 0; y <= page.getMediaBox().getHeight();y = (y+imageXObject.getHeight()+HEIGHT_STEP)){
for(int x= 0; x <= page.getMediaBox().getWidth();x = (x+imageXObject.getWidth()+WIDTH_STEP)){
cs.saveGraphicsState();
Matrix rotate = new Matrix();
rotate.rotate(Math.toRadians(THETA));
cs.transform(rotate);
cs.drawImage(imageXObject, x, y);
cs.restoreGraphicsState();
}
}
cs.close();
}
doc.save(outputStream);
doc.close();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public static PDImageXObject createFromFileByExtension(InputStream inputStream,String filename, PDDocument doc) throws IOException
{
int dot = filename.lastIndexOf('.');
if (dot == -1)
{
throw new IllegalArgumentException("Image type not supported: " + filename);
}
String ext = filename.substring(dot + 1).toLowerCase();
if ("jpg".equals(ext) || "jpeg".equals(ext))
{
PDImageXObject imageXObject = JPEGFactory.createFromStream(doc, inputStream);
inputStream.close();
return imageXObject;
}
if ("gif".equals(ext) || "bmp".equals(ext) || "png".equals(ext))
{
BufferedImage bim = ImageIO.read(inputStream);
return LosslessFactory.createFromImage(doc, bim);
}
throw new IllegalArgumentException("Image type not supported: " + filename);
}
}
本身使用free spire .doc for java操作word文档加水印非常简单,奈何开源版有官方文字。不得已使用free spire .doc for java + apache poi形式
@Component
public class WordWaterMarker implements MyWaterMarker {
private static final String WATER_MARK_PATH = "watermark/watermark-rotate.png";
private static final Integer SCALING = 120;
@Override
public String getFileType(String mimeType) {
return MimeTypeFileType.WORD.findFileType(mimeType);
}
@Override
public Boolean watermark(InputStream inputStream, OutputStream outputStream, String mimeType) {
ByteArrayOutputStream spire = null;
try {
spire = new ByteArrayOutputStream();
//加载Word文档
Document document = new Document();
document.loadFromStream(inputStream, FileFormat.Auto);
//获取第一个节,由于Free Spire for java 会在word开头添加文字 The document was created with Spire.DOC for Java
//解决方案是 ,先添加空白页,然后使用Apache POI 删除空白页
Section section1 = new Section(document);
document.getSections().insert(0,section1);
//创建PictureWatermark实例
PictureWatermark picture = new PictureWatermark();
ClassPathResource classPathResource = new ClassPathResource(WATER_MARK_PATH);
//设置水印图片属性
picture.setPicture(classPathResource.getInputStream());
picture.setScaling(SCALING);
picture.isWashout(false);
//添加水印图片到文档
document.setWatermark(picture);
//保存结果文件
document.saveToFile(spire, FileFormat.Auto );
//POI 去除第一行空白页
ByteArrayInputStream swapStream = new ByteArrayInputStream(spire.toByteArray());
XWPFDocument document1 = new XWPFDocument(swapStream);
document1.removeBodyElement(0);
document1.write(outputStream);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
if (spire != null) {
try {
spire.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
在项目中,加水印用上设计模式:策略模式+工厂模式。
分析:
水印功能可以看做针对不同文件做水印操作。那就可以抽象出水印接口
/**
* 策略模式
*/
public interface MyWaterMarker {
/**
* 获取流大小,反射获取contentWritten
* 目前没找到其他好方法
* @param res
* @return
*/
default Long getLength(HttpServletResponse res){
try {
if(res instanceof OnCommittedResponseWrapper){
Field contentWritten = res.getClass().getSuperclass().getDeclaredField("contentWritten");
contentWritten.setAccessible(true);
return (long) contentWritten.get(res);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取文件类型
* @param mimeType
* @return
*/
String getFileType(String mimeType);
/**
* 是否支持
* @param mimeType
* @return
*/
default Boolean support(String mimeType){
return ObjectUtils.isNotEmpty(getFileType(mimeType));
}
/**
* 加水印
* @param inputStream
* @param outputStream
* @param mimeType
*/
Boolean watermark(InputStream inputStream, OutputStream outputStream, String mimeType);
}
图片水印、pdf水印、word水印分别实现接口来实现不同类型的水印功能
ImageWaterMarker
@Component
public class ImageWaterMarker implements MyWaterMarker {
private static final int HEIGHT_STEP = 100;
private static final int WIDTH_STEP = 80;
private static final int ROTATE =150;
private static final String WATER_MARK_PATH = "watermark/watermark.png";
@Override
public String getFileType(String mimeType) {
return MimeTypeFileType.IMAGE.findFileType(mimeType);
}
@Override
public Boolean watermark(InputStream inputStream, OutputStream outputStream, String mimeType) {
try {
BufferedImage original = ImageIO.read(inputStream);
String fileType = getFileType(mimeType);
ClassPathResource classPathResource = new ClassPathResource(WATER_MARK_PATH);
//水印图片
BufferedImage markImage = ImageIO.read(classPathResource.getInputStream());
// determine image type and handle correct transparency
Graphics2D g2d = original.createGraphics();
//透明度
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.7f));
//角度
g2d.rotate(ROTATE);
int canvasHeight = original.getHeight();
int canvasWidth = original.getWidth();
int markHeight = markImage.getHeight();
int markWidth = markImage.getWidth();
for(int i=-canvasHeight;i<canvasWidth+canvasHeight;i=i+markWidth+WIDTH_STEP){
for(int j=-canvasWidth;j<canvasHeight+canvasWidth;j=j+markHeight+HEIGHT_STEP){
g2d.drawImage(markImage,i,j,markImage.getWidth(),markImage.getHeight(),null);
}
}
g2d.dispose();
ImageIO.write(original, fileType, outputStream);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
}
PdfWaterMarker
@Component
public class PdfWaterMarker implements MyWaterMarker {
private static final int HEIGHT_STEP = 100;
private static final int WIDTH_STEP = 80;
private static final double THETA = 30;
private static final String WATER_MARK_PATH = "watermark/watermark.png";
@Override
public String getFileType(String mimeType) {
return MimeTypeFileType.PDF.findFileType(mimeType);
}
@Override
public Boolean watermark(InputStream inputStream, OutputStream outputStream, String mimeType) {
try {
//打开pdf文件
PDDocument doc = PDDocument.load(inputStream);
doc.setAllSecurityToBeRemoved(true);
//遍历pdf所有页
for (PDPage page : doc.getPages()) {
ClassPathResource classPathResource = new ClassPathResource(WATER_MARK_PATH);
//创建PDImageXObject对象
PDImageXObject imageXObject = createFromFileByExtension(classPathResource.getInputStream(),classPathResource.getFilename(),doc);
PDPageContentStream cs = new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.APPEND, true, true);
PDExtendedGraphicsState pdExtGfxState = new PDExtendedGraphicsState();
// 水印透明度
pdExtGfxState.setNonStrokingAlphaConstant(0.8f);
pdExtGfxState.setAlphaSourceFlag(true);
pdExtGfxState.getCOSObject().setItem(COSName.BM, COSName.MULTIPLY);
cs.setGraphicsStateParameters(pdExtGfxState);
for(int y= 0; y <= page.getMediaBox().getHeight();y = (y+imageXObject.getHeight()+HEIGHT_STEP)){
for(int x= 0; x <= page.getMediaBox().getWidth();x = (x+imageXObject.getWidth()+WIDTH_STEP)){
cs.saveGraphicsState();
Matrix rotate = new Matrix();
rotate.rotate(Math.toRadians(THETA));
cs.transform(rotate);
cs.drawImage(imageXObject, x, y);
cs.restoreGraphicsState();
}
}
cs.close();
}
doc.save(outputStream);
doc.close();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public static PDImageXObject createFromFileByExtension(InputStream inputStream,String filename, PDDocument doc) throws IOException
{
int dot = filename.lastIndexOf('.');
if (dot == -1)
{
throw new IllegalArgumentException("Image type not supported: " + filename);
}
String ext = filename.substring(dot + 1).toLowerCase();
if ("jpg".equals(ext) || "jpeg".equals(ext))
{
PDImageXObject imageXObject = JPEGFactory.createFromStream(doc, inputStream);
inputStream.close();
return imageXObject;
}
if ("gif".equals(ext) || "bmp".equals(ext) || "png".equals(ext))
{
BufferedImage bim = ImageIO.read(inputStream);
return LosslessFactory.createFromImage(doc, bim);
}
throw new IllegalArgumentException("Image type not supported: " + filename);
}
}
WordWaterMarker
@Component
public class WordWaterMarker implements MyWaterMarker {
private static final String WATER_MARK_PATH = "watermark/watermark-rotate.png";
private static final Integer SCALING = 120;
@Override
public String getFileType(String mimeType) {
return MimeTypeFileType.WORD.findFileType(mimeType);
}
@Override
public Boolean watermark(InputStream inputStream, OutputStream outputStream, String mimeType) {
ByteArrayOutputStream spire = null;
try {
spire = new ByteArrayOutputStream();
//加载Word文档
Document document = new Document();
document.loadFromStream(inputStream, FileFormat.Auto);
//获取第一个节,由于Free Spire for java 会在word开头添加文字 The document was created with Spire.DOC for Java
//解决方案是 ,先添加空白页,然后使用Apache POI 删除空白页
Section section1 = new Section(document);
document.getSections().insert(0,section1);
//创建PictureWatermark实例
PictureWatermark picture = new PictureWatermark();
ClassPathResource classPathResource = new ClassPathResource(WATER_MARK_PATH);
//设置水印图片属性
picture.setPicture(classPathResource.getInputStream());
picture.setScaling(SCALING);
picture.isWashout(false);
//添加水印图片到文档
document.setWatermark(picture);
//保存结果文件
document.saveToFile(spire, FileFormat.Auto );
//POI 去除第一行空白页
ByteArrayInputStream swapStream = new ByteArrayInputStream(spire.toByteArray());
XWPFDocument document1 = new XWPFDocument(swapStream);
document1.removeBodyElement(0);
document1.write(outputStream);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
if (spire != null) {
try {
spire.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
水印功能实现类作为bean放入spring容器中。在生成逻辑中,是根据文件类型做判断,接口中提供了support方法来作为类是否支持文件水印。此时需要一个类来管理所有的水印功能类,创建一个RivTrustWaterMarkFactory。利用Spring bean的生命周期,在所有bean加载完成后,为RivTrustWaterMarkFactory整合WordRivTrustWaterMarker,PdfRivTrustWaterMarker,ImageRivTrustWaterMarker
方法有两种
MyWaterMarkFactory实现ApplicationListener监听ContextRefreshedEvent事件
MyWaterMarkFactory实现CommandLineRunner接口实现 run(String… args)方法
@Override
public void run(String... args) throws Exception {
initStrategyMap();
}
RivTrustWaterMarkFactory
/**
* 工厂模式,
*/
@Component
public class MyWaterMarkFactory implements ApplicationListener, ApplicationContextAware {
private List<MyWaterMarker> waterMarkers = new ArrayList<>();
private ApplicationContext applicationContext;
@Resource
private WithOutWaterMarker withOutWaterMarker;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 监听事件,从spring工厂中获取所有RivTrustWaterMark Bean
* @param event
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
if( event instanceof ContextRefreshedEvent){
Map<String, MyWaterMarker> beansOfType = this.applicationContext.getBeansOfType(RivTrustWaterMarker.class);
waterMarkers = beansOfType.values().stream().collect(Collectors.toList());
}
}
public RivTrustWaterMarker support(String filename) {
for (RivTrustWaterMarker rivTrustWaterMarker : waterMarkers) {
if(rivTrustWaterMarker.support( filename)){
return rivTrustWaterMarker;
}
}
return withOutTrustWaterMarker;
}
}