Step By Step(Java 2D图形篇<二>)
2011-10-06 09:19
399 查看
本篇将继续介绍Java 2D 图形部分的内容。
5. 坐标变换:
坐标变换是图形编程中非常重要的一项技术,该技术在基于3D的开发中应用更为普遍,我们这里将介绍Java 2D中四种基本的变换:
1) 比例缩放(scale):放大和缩小从一个固定点出发的所有距离;
2) 旋转(rotate):环绕着一个固定中心旋转所有点;
3) 平移(translate):将所有的点移动一个固定量;
4) 切变(shear):使一个线条固定不变,再按照与该固定线条之间的距离,成比例地将与该线条平行的各个线条"滑动"一个距离量。
这里提及的四种变换规则可以组合在一起使用,然而需要注意的是,在组合时,Graphics2D是按照相反的顺序执行变换的,如:
对于上面代码中的组合变换,translate将会被最先执行,其次是scale,最后才是rotate。
在Java 2D的API中提供了AffineTransform类用于描述各种变换,该类提供了一组静态工厂方法用于获取不同的AffineTransform子类,如:getRotateInstance、getScaleInstance、getTranslateInstance和getShearInstance。之后我们再通过Graphics2D.setTransform()方法设置当前Graphics的坐标变换对象。然而在实际的应用中,我们经常需要调用的却是Graphics2D.transform()方法以组合当前的变换和新设置的变换,而setTransform()域方法则是完全替换当前Graphics的变换为新设置的变换。
6. 剪切:
通过在图形上下文中设置一个剪切形状,可以将所有的绘图操作限制在该剪切形状内部来进行,如:
g2.setClip(clipShape);
g2.draw(Shape);
setClip()方法和坐标变换中setTransform()方法有一个非常重要的相似之处,他们都会直接替换Graphics中已有的坐标变换或剪切区域。鉴于此,Java 2D 在剪切中同样提供了另一个方法clip(),该方法和坐标变换中的transform等方法相似,会将当前的剪切区域和已有的剪切区域进行合并,并形成新的剪切区域,如:g2.clip(clipShape)。
7. 透明与组合:
在标准RGB颜色模型中,每种颜色都是由红、绿和蓝这三种成分来描述的。用它来描述透明或者部分透明的图像区域也是非常方便的。在Java 2D 中,透明是由一个透明通道来描述的。每个像素,除了它的红、绿和蓝色部分外,还有一个介于0(完全透明)和1(完全不透名)之间的透明度(alpha)值。当两个形状重叠在一起时,必须把源像素和目标像素的颜色和透明度值混合或者组合起来。Java 2D中提供了12中组合方式,而我们这里只是介绍缺省情况下的SRC_OVER原则。
Alpha_D:目标形状的Alpha值
Alpha_S:源形状的Alpha值
在进行混合计算时,源像素颜色的权值为Alpha_S, 目标像素颜色的权值为(1 - Alpha_S) * Alpha_D。
在Java 2D中可以通过Graphics2D.setComposite()方法设定组合接口。该接口的具体实现类可以通过AlphaComposite提供的一组静态工厂方法获取,如:
上面代码中应用的组合原则是SRC_OVER,透明度是0.5。
8. 绘图提示:
Java 2D中提供了一些绘图提示的选项,以帮助我们在渲染时能够达到一种绘图速度和质量之间的平衡关系。在这些提示中最为常用的主要有以下三个:
KEY_ANTIALIASING 打开或者关闭形状抗锯齿特征。
VALUE_ANTIALIAS_ON
VALUE_ANTIALIAS_OFF
KEY_TEXT_ANTIALIASING 打开或者关闭字体的抗锯齿特征
VALUE_TEXT_ANTIALIAS_ON
VALUE_TEXT_ANTIALIAS_OFF
KEY_INTERPOLATION 当对形状进行缩放或者旋转操作时,为像素的插换选择一个规则
VALUE_INTERPOLATION_NEAREST_NEIGHBOR
VALUE_INTERPOLATION_BILINEAR
VALUE_INTERPOLATION_BICUBIC
下面的代码演示了如何设定指定的绘图提示:
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
该语句打开了g2的图形抗锯齿功能。下面的示例代码主要演示了抗锯齿功能。
9. 图像的读取器和写入器:
从Java SE6 开始,JDK中提供了对GIF、JPEG、PNG、BMP等文件格式的读写支持。我们可以通过ImageIO.read和ImageIO.write两个静态方法分别读取和写入图形文件。ImageIO会根据文件的类型选择适当的读取器和写入器,而文件的类型主要是通过目标文件的扩展名和文件中的Magic Number来判断的。如果没有找到合适的读取器,ImageIO.read将返回null。
通常而言,我们在进行一些图形操作时,都是基于Java 2D中的BufferedImage类协助完成的,因此我们在读取图形文件时,我们的目标也是将图形文件包含的数据读入BufferedImage对象中,以供后用。
1) 直接读取只是包含一幅图像的数据文件:
2) 通过ImageReader读取包含多幅图像的数据文件:
读取该类型的图像文件需要以下三个步骤:
2.1)将图形文件映射到图形输入流中(ImageInputStream),作为读取器的数据输入源;
2.2)根据图形文件的类型选择合适的ImageReader,由于图形数据的读取和解析;
2.3)将图形输入流绑定到ImageReader,并迭代读取出所有的图像数据到不同的BufferedImage。
这里我们需要制作出包含多幅图像的测试文件。
3) 将一幅图像直接写入目标文件:
4) 通过ImageWriter将多幅图像顺序写入目标文件:
5) 通过ImageIO.read()和ImageIO.write()方法进行图形文件格式转换:
这里只是给出从PNG到JPEG的例子,事实上其他类型的图像文件之间也可以通过该方式进行转换。
6) Icon文件的读入:
5. 坐标变换:
坐标变换是图形编程中非常重要的一项技术,该技术在基于3D的开发中应用更为普遍,我们这里将介绍Java 2D中四种基本的变换:
1) 比例缩放(scale):放大和缩小从一个固定点出发的所有距离;
2) 旋转(rotate):环绕着一个固定中心旋转所有点;
3) 平移(translate):将所有的点移动一个固定量;
4) 切变(shear):使一个线条固定不变,再按照与该固定线条之间的距离,成比例地将与该线条平行的各个线条"滑动"一个距离量。
这里提及的四种变换规则可以组合在一起使用,然而需要注意的是,在组合时,Graphics2D是按照相反的顺序执行变换的,如:
public void paint(Graphics g) { Graphics2D g2 = (Graphics2D)g; g2.rotate(angle); g2.scale(2,2); g2.translate(x,y); }
对于上面代码中的组合变换,translate将会被最先执行,其次是scale,最后才是rotate。
在Java 2D的API中提供了AffineTransform类用于描述各种变换,该类提供了一组静态工厂方法用于获取不同的AffineTransform子类,如:getRotateInstance、getScaleInstance、getTranslateInstance和getShearInstance。之后我们再通过Graphics2D.setTransform()方法设置当前Graphics的坐标变换对象。然而在实际的应用中,我们经常需要调用的却是Graphics2D.transform()方法以组合当前的变换和新设置的变换,而setTransform()域方法则是完全替换当前Graphics的变换为新设置的变换。
public class MyTest extends JPanel { public static void main(String[] args) { JFrame frame = new JFrame(); frame.setTitle("AffineTransform"); frame.setSize(1000, 600); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); Container contentPane = frame.getContentPane(); contentPane.add(new MyTest()); frame.show(); } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.orange); g.drawLine(0, getHeight()/4, getWidth(), getHeight()/4); g.drawLine(0, getHeight()/2, getWidth(), getHeight()/2); g.drawLine(0, getHeight()*3/4, getWidth(), getHeight()*3/4); paintWithGraphicsMethod(g); paintWithTransform(g); paintWithSetTransform(g); paintWithStaticTransformInstance(g); } //通过Graphics自身提供的变换方法进行变换 private void paintWithGraphicsMethod(Graphics g) { //Graphics中的变换都是组合变换方式, Graphics2D g2d = (Graphics2D) g; AffineTransform oldTrans = g2d.getTransform(); g2d.setColor(Color.yellow); g2d.fillRect(20, 20, 60, 60); //1. 基于原点进行平移变换 g2d.translate(160, 0); g2d.fillRect(20, 20, 60, 60); //2. 基于1)平移变换后继续平移变换 g2d.translate(160, 0); //3. 在2)平移的基础上进行旋转变换 g2d.rotate(Math.PI / 4); g2d.fillRect(20, 20, 60, 60); //4. 这里需要特别注意,由于3)中组合了旋转变换,因此这里再直接进行 //平移变换x时,因为坐标系的角度发生了变化,所以不能得到期望的平移 //效果,在平移前需要将3)中的旋转再反向旋转回来。 g2d.rotate(-Math.PI / 4); g2d.translate(160, 0); //5. 在4)的基础上做放大变换 g2d.scale(1.5, 1.5); g2d.fillRect(20, 20, 60, 60); //6. 做切变变化,可以通过调整shear()方法的两个参数,进一步掌握切变变换 g2d.translate(160, 0); g2d.shear(0.5, 0); g2d.fillRect(20, 20, 60, 60); //7. 这里需要还原,否则会影响后面的变换 g2d.setTransform(oldTrans); } //该方法和paintWithGraphicsMethod机制是一致的,只是应用的方法改为 //AffineTransform提供的变换方法 private void paintWithTransform(Graphics g) { Graphics2D g2d = (Graphics2D) g; AffineTransform oldTrans = g2d.getTransform(); //1. 先将坐标系的原点移到第二个泳道中,以便后面的代码可以和其他 //函数中的代码保持一致 g2d.translate(0, getHeight()/4); g2d.setColor(Color.red); g2d.fillRect(20, 20, 60, 60); AffineTransform tx1 = new AffineTransform(); tx1.translate(160, 0); //2. 这里用到的transform方法在Graphics对象内部也是组合方式的, //换句话说,后面的transform方法将会基于本次变化的结果。 g2d.transform(tx1); g2d.fillRect(20, 20, 60, 60); AffineTransform tx2 = new AffineTransform(); tx2.translate(160, 0); tx2.rotate(Math.PI / 4); //3. 本次transform将基于1)中transform的结果并进行组合 g2d.transform(tx2); g2d.fillRect(20, 20, 60, 60); AffineTransform tx3 = new AffineTransform(); //4. 这里的rotate的作用和上面的paintWithGraphicsMethod()方法的4)是一致的。 tx3.rotate(-Math.PI / 4); tx3.translate(160, 0); tx3.scale(1.5, 1.5); g2d.transform(tx3); g2d.fillRect(20, 20, 60, 60); //5. 切变变换 AffineTransform tx4 = new AffineTransform(); tx4.shear(0.5, 0); tx4.translate(160, 0); g2d.transform(tx4); g2d.fillRect(20, 20, 60, 60); g2d.setTransform(oldTrans); } //通过setTransform方法基于绝对坐标进行变换 private void paintWithSetTransform(Graphics g) { Graphics2D g2d = (Graphics2D) g; AffineTransform oldTrans = g2d.getTransform(); //1. 先将坐标系的原点移到第三个泳道中,以便后面的代码可以和其他 //函数中的代码保持一致 g2d.translate(0, getHeight()/2); g2d.setColor(Color.blue); g2d.fillRect(20, 20, 60, 60); AffineTransform tx1 = new AffineTransform(); //2. 由于setTransform方法是直接替换为参数中的变换对象,之前驻留在Graphics中的 //变换对象都不在起作用了,因此这里在平移时,需要基于最原始的(窗体左上角)原点坐标 //进行平移变换,可以理解为绝对平移变换。 //可以看到translate中的y参数是相对于最初原点的平移距离。 tx1.translate(160, getHeight()/2); //3. 将g2d中的变换对象直接替换为参数对象。 g2d.setTransform(tx1); g2d.fillRect(20, 20, 60, 60); AffineTransform tx2 = new AffineTransform(); //4. 道理和上面的代码一样,可以看到这个时候x参数也设定为绝对距离了。 tx2.translate(320, getHeight()/2); tx2.rotate(Math.PI / 4); g2d.setTransform(tx2); g2d.fillRect(20, 20, 60, 60); AffineTransform tx3 = new AffineTransform(); //5. 需要注意的是tx3.rotate(-Math.PI / 4) 方法不需要再被调用了,因为 //setTransform并不和之前的变换组合。 tx3.translate(480, getHeight()/2); tx3.scale(1.5, 1.5); g2d.setTransform(tx3); g2d.fillRect(20, 20, 60, 60); //6. 切变变换,为了保持和其他泳道显示的结果一致,这里需要重新设定scale变换 AffineTransform tx4 = new AffineTransform(); tx4.translate(720, getHeight()/2); tx4.shear(0.5, 0); tx4.scale(1.5, 1.5); g2d.setTransform(tx4); g2d.fillRect(20, 20, 60, 60); g2d.setTransform(oldTrans); } //通过AffineTransform的静态工厂方法获取AffineTransform的具体子类 //再通过已经设定好变换的AffineTransform和形状的原始坐标生成目标 //形状,由于目标形状中已经是变换后的形状,因此Graphics2D可以直接渲染即可 private void paintWithStaticTransformInstance(Graphics g) { Graphics2D g2d = (Graphics2D) g; AffineTransform oldTrans = g2d.getTransform(); //1. 先将坐标系的原点移到第四个泳道中,以便后面的代码可以和其他 //函数中的代码保持一致 g2d.translate(0, getHeight()*3/4); g2d.setColor(Color.green); g2d.fillRect(20, 20, 60, 60); AffineTransform tx1 = AffineTransform.getTranslateInstance(160, 0); Rectangle rect = new Rectangle(20, 20, 60, 60); Shape newShape = tx1.createTransformedShape(rect); g2d.fill(newShape); //2. 由于是直接通过Graphics2D渲染目标形状,因此这些变换并没有在 //Graphics2D中进行过组合,因此每次变换都是独立的变换,需要指定 //距离原始坐标的绝对距离 AffineTransform tx2 = AffineTransform.getTranslateInstance(320, 0); tx2.rotate(Math.PI / 4); newShape = tx2.createTransformedShape(rect); g2d.fill(newShape); AffineTransform tx3 = AffineTransform.getTranslateInstance(480, 0); tx3.scale(1.5, 1.5); newShape = tx3.createTransformedShape(rect); g2d.fill(newShape); AffineTransform tx4 = AffineTransform.getTranslateInstance(720, 0); tx4.scale(1.5, 1.5); tx4.shear(0.5, 0); newShape = tx4.createTransformedShape(rect); g2d.fill(newShape); g2d.setTransform(oldTrans); } }
6. 剪切:
通过在图形上下文中设置一个剪切形状,可以将所有的绘图操作限制在该剪切形状内部来进行,如:
g2.setClip(clipShape);
g2.draw(Shape);
setClip()方法和坐标变换中setTransform()方法有一个非常重要的相似之处,他们都会直接替换Graphics中已有的坐标变换或剪切区域。鉴于此,Java 2D 在剪切中同样提供了另一个方法clip(),该方法和坐标变换中的transform等方法相似,会将当前的剪切区域和已有的剪切区域进行合并,并形成新的剪切区域,如:g2.clip(clipShape)。
public class MyTest extends JPanel { public static void main(String[] args) { JFrame frame = new JFrame(); frame.setTitle("Clip"); frame.setSize(600, 600); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); Container contentPane = frame.getContentPane(); contentPane.add(new MyTest()); frame.show(); } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.orange); g.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2); paintClipRectangle(g); } private void paintClipRectangle(Graphics g) { Graphics2D g2 = (Graphics2D) g; int w = getWidth(); int h = getHeight() / 2; //设定裁剪区域为当前的椭圆,由于使用的setClip,因此不会和Graphics中 //已经设定的clip进行组合。 Ellipse2D e = new Ellipse2D.Float(w / 4.0f, h / 4.0f, w / 2.0f, h / 2.0f); g2.setClip(e); g2.setColor(Color.yellow); g2.fillRect(0, 0, w, h); //打印出当前剪切区域的矩形边界,这里的bounds为椭圆剪切区域的外切矩形 System.out.println(g2.getClipBounds()); e = new Ellipse2D.Float(w / 4.0f, h / 4.0f + h, w / 2.0f, h / 2.0f); g2.setClip(e); g2.setColor(Color.green); g2.fillRect(0, h, w, h); System.out.println("Before clip: " + g2.getClipBounds()); //和Graphics中已经存在的剪切区域组合,取两个剪切区域的交集。 //如果没有交集,则无法绘制任何形状。 Rectangle r = new Rectangle(w / 2, h , w / 2, h ); g2.clip(r); g2.setColor(Color.blue); g2.fillRect(0, h, w, h); System.out.println("After clip: " + g2.getClipBounds()); } }
7. 透明与组合:
在标准RGB颜色模型中,每种颜色都是由红、绿和蓝这三种成分来描述的。用它来描述透明或者部分透明的图像区域也是非常方便的。在Java 2D 中,透明是由一个透明通道来描述的。每个像素,除了它的红、绿和蓝色部分外,还有一个介于0(完全透明)和1(完全不透名)之间的透明度(alpha)值。当两个形状重叠在一起时,必须把源像素和目标像素的颜色和透明度值混合或者组合起来。Java 2D中提供了12中组合方式,而我们这里只是介绍缺省情况下的SRC_OVER原则。
Alpha_D:目标形状的Alpha值
Alpha_S:源形状的Alpha值
在进行混合计算时,源像素颜色的权值为Alpha_S, 目标像素颜色的权值为(1 - Alpha_S) * Alpha_D。
在Java 2D中可以通过Graphics2D.setComposite()方法设定组合接口。该接口的具体实现类可以通过AlphaComposite提供的一组静态工厂方法获取,如:
public void paint(Graphics g) { int rule = AlphaComposite.SRC_OVER; float alpha = 0.5f; Graphics2D g2 = (Graphics2D)g; g2.setComposite(AlphaComposite.getInstance(rule,alpha)); g2.setPaint(Color.blue); g2.fill(new Rectangle2D.Double(2.0,2.0,10.0.10.0)); }
上面代码中应用的组合原则是SRC_OVER,透明度是0.5。
public class MyTest extends JPanel { public static void main(String[] args) { JFrame frame = new JFrame(); frame.setTitle("Composition"); frame.setSize(400, 120); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); Container contentPane = frame.getContentPane(); contentPane.add(new MyTest()); frame.show(); } public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; //获取一个组合原则为SRC_OVER,透明度为0.5的AlphaComposite //可以通过修改不同的透明度值,以观察该组合规则的效果。 AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f); //derive()方法是jdk 1.6中新加入的方法,从使用视角看,是非常方便的builder方法 //下面被注释的语句和上面语句中获取的AlphaComposite对象是等同的。 //AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER).derive(0.5f); BufferedImage buffImg = new BufferedImage(60, 60, BufferedImage.TYPE_INT_ARGB); Graphics2D gbi = buffImg.createGraphics(); gbi.setPaint(Color.red); gbi.fillRect(0, 0, 40, 40); gbi.setComposite(ac); gbi.setPaint(Color.green); gbi.fillRect(5, 5, 40, 40); g2d.drawImage(buffImg, 20, 20, null); gbi.dispose(); } }
8. 绘图提示:
Java 2D中提供了一些绘图提示的选项,以帮助我们在渲染时能够达到一种绘图速度和质量之间的平衡关系。在这些提示中最为常用的主要有以下三个:
KEY_ANTIALIASING 打开或者关闭形状抗锯齿特征。
VALUE_ANTIALIAS_ON
VALUE_ANTIALIAS_OFF
KEY_TEXT_ANTIALIASING 打开或者关闭字体的抗锯齿特征
VALUE_TEXT_ANTIALIAS_ON
VALUE_TEXT_ANTIALIAS_OFF
KEY_INTERPOLATION 当对形状进行缩放或者旋转操作时,为像素的插换选择一个规则
VALUE_INTERPOLATION_NEAREST_NEIGHBOR
VALUE_INTERPOLATION_BILINEAR
VALUE_INTERPOLATION_BICUBIC
下面的代码演示了如何设定指定的绘图提示:
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
该语句打开了g2的图形抗锯齿功能。下面的示例代码主要演示了抗锯齿功能。
public class MyTest extends JPanel { private boolean isOutput = false; public static void main(String[] args) { JFrame frame = new JFrame(); frame.setTitle("RenderingHints"); frame.setSize(300, 400); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); Container contentPane = frame.getContentPane(); contentPane.add(new MyTest()); frame.show(); } public void paintComponent(Graphics g) { super.paintComponent(g); //画出四条泳道 g.setColor(Color.orange); g.drawLine(0, getHeight()/4, getWidth(), getHeight()/4); g.drawLine(0, getHeight()/2, getWidth(), getHeight()/2); g.drawLine(0, getHeight()*3/4, getWidth(), getHeight()*3/4); paintAntialiasingText(g); paintAntialiasingShape(g); } //打开和关闭文字抗锯齿提示 private void paintAntialiasingText(Graphics g) { Graphics2D g2d = (Graphics2D) g; // 将字体放大,对比后效果更明显 Font fi = new Font("SansSerif", Font.BOLD + Font.ITALIC, 30); g2d.setFont(fi); // 1. 在第一泳道打开文字抗锯齿 g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2d.drawString("Hello World", 20, 40); RenderingHints rhints = g2d.getRenderingHints(); //测试文字抗锯齿特征是否已经打开 boolean antialiasTextOn = rhints .containsValue(RenderingHints.VALUE_TEXT_ANTIALIAS_ON); if (!isOutput) System.out.println("VALUE_TEXT_ANTIALIAS_ON is " + (antialiasTextOn ? "on." : "off.")); // 2. 在第二泳道关闭文字抗锯齿 g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); g2d.drawString("Hello World", 20, getHeight() / 4 + 40); rhints = g2d.getRenderingHints(); antialiasTextOn = rhints .containsValue(RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); if (!isOutput) System.out.println("VALUE_TEXT_ANTIALIAS_OFF is " + (antialiasTextOn ? "on." : "off.")); isOutput = true; } //打开和关闭图形抗锯齿提示 private void paintAntialiasingShape(Graphics g) { Graphics2D g2d = (Graphics2D) g; //将笔划设置更粗,这样对比效果更明显 Stroke s = new BasicStroke(2); g2d.setStroke(s); Ellipse2D e1 = new Ellipse2D.Double(20, getHeight()/2 + 20,60,60); //3. 在第三泳道打开图形抗锯齿 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.draw(e1); Ellipse2D e2 = new Ellipse2D.Double(20, getHeight()*3/4 + 20,60,60); //4. 在第四泳道关闭图形抗锯齿 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); g2d.draw(e2); } }
9. 图像的读取器和写入器:
从Java SE6 开始,JDK中提供了对GIF、JPEG、PNG、BMP等文件格式的读写支持。我们可以通过ImageIO.read和ImageIO.write两个静态方法分别读取和写入图形文件。ImageIO会根据文件的类型选择适当的读取器和写入器,而文件的类型主要是通过目标文件的扩展名和文件中的Magic Number来判断的。如果没有找到合适的读取器,ImageIO.read将返回null。
通常而言,我们在进行一些图形操作时,都是基于Java 2D中的BufferedImage类协助完成的,因此我们在读取图形文件时,我们的目标也是将图形文件包含的数据读入BufferedImage对象中,以供后用。
1) 直接读取只是包含一幅图像的数据文件:
public class MyTest extends JPanel { private static BufferedImage image = null; public static void main(String[] args) throws IOException { JFrame frame = new JFrame(); frame.setTitle("ImageIO Read"); frame.setSize(300, 400); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); Container contentPane = frame.getContentPane(); readByFile(); // or readByInputStream(); //两种方式可以得到相同的效果,只是由于readByInputStream()在读入的 //过程中使用了BufferedInputStream,读取效率可能会更高。 contentPane.add(new MyTest()); frame.show(); } private static void readByFile() throws IOException { File sourceimage = new File("D:/desktop.png"); image = (BufferedImage)ImageIO.read(sourceimage); } private static void readByInputStream() throws IOException { InputStream bin = new BufferedInputStream(new FileInputStream("D:/desktop.png")); image = ImageIO.read(bin); bin.close(); } public void paintComponent(Graphics g) { super.paintComponent(g); if (image == null) return; g.drawImage(image, 0, 0, null); } }
2) 通过ImageReader读取包含多幅图像的数据文件:
读取该类型的图像文件需要以下三个步骤:
2.1)将图形文件映射到图形输入流中(ImageInputStream),作为读取器的数据输入源;
2.2)根据图形文件的类型选择合适的ImageReader,由于图形数据的读取和解析;
2.3)将图形输入流绑定到ImageReader,并迭代读取出所有的图像数据到不同的BufferedImage。
这里我们需要制作出包含多幅图像的测试文件。
public class MyTest extends JPanel { private BufferedImage[] images; private int imageIndex = 0; public static void main(String[] args) { JFrame frame = new JFrame(); frame.setTitle("ImageReader"); frame.setSize(300, 400); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); Container contentPane = frame.getContentPane(); contentPane.add(new MyTest()); frame.show(); } public MyTest() { try { loadImage(); } catch (Exception e) { e.printStackTrace(); } } private void loadImage() throws Exception { String filename = "D:/desktop.gif"; // 1. 获取图形文件的输入流 FileInputStream inputStream = new FileInputStream(filename); // 2. 基于文件输入流生成图形输入流作为ImageReader的数据源 ImageInputStream imageInputStream = ImageIO.createImageInputStream(inputStream); // 3. 通过ImageIO的静态工厂方法获取指定类型图形文件的候选读取器集合的迭代器 Iterator<ImageReader> readers = ImageIO.getImageReadersBySuffix("gif"); // 4. 一般而言,可以选择第一个读取器作为该类型图形文件的读取器。 ImageReader imageReader = readers.next(); // 5. 给ImageReader设定输入源,同时将第二个参数设定为false //以便于后面获取Images的数量。 imageReader.setInput(imageInputStream, false); // 6. 获取图像的数量。 // 参数说明:由于在某些图形文件的头部并没有给出该文件包含的图像数量, // 因此如果该参数设置为false,该这种情况下函数将返回-1。如果参数为 // true呢?读取器将主动遍历整个图形文件并统计出图像的数量后返回。需要 // 注意的是,这样的操作可能会带来效率的开销。 int num = imageReader.getNumImages(true); // 7. 利用读取器将图形数据从图形文件依次读取到BufferedImage。 images = new BufferedImage[num]; for (int i = 0; i < num; ++i) images[i] = imageReader.read(i); imageReader.dispose(); // 8. 这两个InputStream的close方法都是需要调用的,imageInputStream将会 //在close的时候关闭并删除其内部作为CacheFile的RandomAccessFile对象。 imageInputStream.close(); inputStream.close(); } public void paintComponent(Graphics g) { super.paintComponent(g); if (images == null) return; g.drawImage(images[imageIndex], 0, 0, null); imageIndex = (imageIndex + 1) % images.length; } }
3) 将一幅图像直接写入目标文件:
public class MyTest { public static void main(String[] args) throws IOException { writeToFile(); } private static void writeToFile() throws IOException { int width = 200, height = 200; BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = bi.createGraphics(); g2.setColor(Color.red); g2.fillRect(0, 0, width - 1, height - 1); ImageIO.write(bi, "png", new File("D:/test.png")); System.out.println("Over."); } }
4) 通过ImageWriter将多幅图像顺序写入目标文件:
public class MyTest { public static void main(String[] args) throws IOException { writeToFile(); } private static void writeToFile() throws IOException { int width = 200, height = 200; BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Color[] colors = { Color.red, Color.black, Color.blue, Color.yellow, Color.gray, Color.green, Color.orange }; Graphics2D g2 = bi.createGraphics(); //1. 这里的操作和通过ImageReader读取多个文件时基本相同, //只是换成了ImageWriter。 Iterator<ImageWriter> imageWriters = ImageIO .getImageWritersByFormatName("gif"); ImageWriter imageWriter = imageWriters.next(); ImageOutputStream ios = ImageIO.createImageOutputStream(new File( "D:/test.gif")); imageWriter.setOutput(ios); //2. 这里需要提前判断一下是否当前的写入器支持顺序写入多个图片的功能 if (imageWriter.canWriteSequence()) { //3. 在顺序写入之前prepare必须调用,否则在写入的时候抛出异常 imageWriter.prepareWriteSequence(null); //4. 开始顺序写入 for (Color c : colors) { g2.setColor(c); g2.fillRect(0, 0, width - 1, height - 1); imageWriter.writeToSequence(new IIOImage(bi, null, null), null); } // 5. 结束写入,有些类似于数据库操作的事物,在操作后需要提交。 imageWriter.endWriteSequence(); } imageWriter.dispose(); ios.close(); System.out.println("Over."); } }
5) 通过ImageIO.read()和ImageIO.write()方法进行图形文件格式转换:
这里只是给出从PNG到JPEG的例子,事实上其他类型的图像文件之间也可以通过该方式进行转换。
public class MyTest { public static void main(String[] args) throws IOException { convertFromPngToJpeg(); } private static void convertFromPngToJpeg() throws IOException { BufferedImage bufferedImage = ImageIO.read(new File("D:/desktop.png")); BufferedImage destImage = new BufferedImage(100,100,BufferedImage.TYPE_INT_RGB); Graphics2D g = destImage.createGraphics(); AffineTransform at = AffineTransform.getScaleInstance(2, 2); //这里通过drawRenderedImage方法将源图像变换后的结果直接写入目的BufferedImage //注意:源图像并未发生变换 g.drawRenderedImage(bufferedImage, at); ImageIO.write(destImage, "JPG", new File("D:/desktop.jpg")); System.out.println("Over."); } }
6) Icon文件的读入:
public class MyTest extends JPanel { public static void main(String[] args) throws IOException { JFrame frame = new JFrame(); frame.setTitle("Read Icon"); frame.setSize(300, 300); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); Container contentPane = frame.getContentPane(); contentPane.add(new MyTest()); frame.show(); } public void paintComponent(Graphics g) { super.paintComponent(g); ImageIcon icon = new ImageIcon("D:/desktop.jpg"); int width = icon.getIconWidth(); int height = icon.getIconHeight(); int x = 0; int y = 0; //1. 利用ImageIcon的功能直接绘制 icon.paintIcon(this, g, x, y); Graphics2D g2d = (Graphics2D)g; //2. 转换到BufferedImage后在绘制 BufferedImage bi = icon.getImage(); g2d.drawImage(bi, x + 100, y + 100, null); System.out.println("Width = " + width + " Height = " + height); } }
相关文章推荐
- Step By Step(Java 2D图形篇<二>)
- Step By Step(Java 2D图形篇<四>)
- Step By Step(Java 2D图形篇<三>)
- Step By Step(Java 2D图形篇<一>)
- Step By Step(Java 2D图形篇<一>)
- Step By Step(Java 常用对象篇<二>)
- Step By Step(Java 常用对象篇<一>)
- Caused by: java.lang.NoSuchMethodException: com.you.entity.sys.Param.<init>()
- 用java源代码学数据结构<二>: Vector 详解
- Java基础语法<二> 字符串String
- 【Java编程】Java学习笔记<二>
- 黑马程序员——Java基础---语法<二>
- java-面向对象<二>-《下篇》
- spring整合jbpm Caused by: java.lang.NoSuchMethodError: org.objectweb.asm.ClassWriter.<init>(Z)V
- 黑马程序员 Java基础<二> 运算符
- Java传递音频给PC端C#程序<二>
- 响应式微服务 in Java 译 --理解响应式微服务和Vert.x<二>
- 黑马程序员——浅谈java中的内部类<二>
- Spring基础_通过java代码装配bean<二>
- Java基础之(三十五)输入输出<二>