list23.docx
《list23.docx》由会员分享,可在线阅读,更多相关《list23.docx(45页珍藏版)》请在冰豆网上搜索。
list23
第23章图像
本章我们来学习AWT的Image类和java.awt.image包,它们为成像(对图像的显示和操作)提供了支持。
一个image只是一个矩形的图形对象,而images是网页设计中的一个重要的组件。
事实上,在NCSA(国际超级计算机应用中心)的Mosaic浏览器中包含标记是促使网络从1993年以来蓬勃发展的原因。
此标记用来将图像内嵌入超文本。
Java发展了这个基本概念,允许图像受到程序控制。
由于图像的重要性,Java为它提供了广泛的支持。
Images是Image类的对象,而Image类是java.awt包的一部分。
Images由java.awt.image中的类对其进行操作。
java.awt.image定义了一大批类和接口,一一对它们进行研究是不可能的,因此我们集中研究其中对成像起基础作用的那一部分。
下面是将在本章里讨论的java.awt.image类:
CropImageFilter
MemoryImageSource
FilteredImageSource
PixelGrabber
ImageFilter
RGBImageFilter
这些是我们将用到的接口:
ImageConsumer
ImageObserver
ImageProducer
除此以外,我们还要讨论Media.Tracker类,它是java.awt的一部分。
23.1文件格式
最初,网页图像仅用GIF一种格式。
这种GIF图像格式由CompuServe在1987年创建,从而使得图像能够在线浏览,因此它非常适合于Internet。
每个GIF图像至多只能有256种颜色。
这种局限使得主要的浏览器厂商在1995年增加了对JPEG图像的支持。
JPEG格式是由一群专业摄影师为了存储全彩色光谱的连续色调的图像而创建的。
这种图像,只要被正确的生成,不但能比由同一源图像编码生成的GIF图像更好的被压缩,而且具有更好的精度。
在几乎所有的情况下,你不必关心或注意在你的程序中用了哪种格式。
Java图像类可以抽象出接口之间的差异。
23.2图像基本操作:
创建,加载和显示
当你对图像进行操作时三种最常见的操作为:
创建图像,加载图像和显示图像。
在Java中,Image类用来指向内存中的图像以及那些必须从外部资源加载的图像。
因此,Java为你提供了创建新的图像对象并加载它的方法,它也提供了显示图像的方法。
让我们依次看一看这些方法。
23.2.1创建一个图像对象
你可能会期望用如下的工具创建一个内存图像(MemoryImage)。
Imagetest=newImage(200,100);//Error--won'twork
但并不是这样。
因为图像必须最终被画在窗口中以便查看,而Image类没有关于它的环境的足够信息来为屏幕生成合适的数据格式。
所以,java.awt的Component类有一个叫做createImage()的方法用来生成图像对象(记住所有的AWT组件都是Component类的子类,因此它们都支持该方法)。
CreateImage()方法有如下两种形式:
ImagecreateImage(ImageProducerimgProd)
ImagecreateImage(intwidth,intheight)
第一种形式返回由imgProd产生的图像,imgProd是一个实现ImageProducer接口的类的对象(稍后我们将讨论producers)。
第二种形式返回具有指定宽度和高度的空图像,如下例所示。
Canvasc=newCanvas();
Imagetest=c.createImage(200,100);
上例生成了一个画布Canvas实例,然后调用createImage()方法来实际生成一个Image对象。
这里,图像是空白的。
以后你将会看到如何对它写数据。
23.2.2加载一个图像
获得一个图像的另一种方法是加载图像。
这通过使用由Applet类定义的getImage()方法来实现。
它有以下形式:
ImagegetImage(URLurl)
ImagegetImage(URLurl,StringimageName)
该方法的第一种形式将参数url所设定的路径下的图像装入一个Image类的对象并将它返回。
第二种形式将参数url所设定的路径下的图像装入一个以参数imageName命名的Image类的对象并将它返回。
23.2.3显示图像
只要你有一个图像,你就可以用drawImage()方法来显示它,drawImage()方法是Graphics类中的一员。
它有几种形式,我们将要用到的一种如下所示:
booleandrawImage(ImageimgObj,intleft,inttop,ImageObserverimgOb)
它显示了由imgObj所传递的图像,其左上角由left和top指定。
imgOb是一个实现了ImageObserver接口的类的引用。
这个接口由所有的AWT组件所实现。
一个imageobserver是一个对象,它能够在图像被加载时对其进行监控。
ImageObserver将在下一节讨论。
用getImage()方法和drawImage()方法很容易加载和显示图像。
这里是一个加载和显示简单图像的小应用程序示例。
文件seattle.jpg被加载,但你能随意用任何GIF或JPG文件来替代它(只要保证它与包含这个小应用程序的HTML文件在同一个目录下)。
/*
*
*
*
*/
importjava.awt.*;
importjava.applet.*;
publicclassSimpleImageLoadextendsApplet
{
Imageimg;
publicvoidinit(){
img=getImage(getDocumentBase(),getParameter("img"));
}
publicvoidpaint(Graphicsg){
g.drawImage(img,0,0,this);
}
}
在init()方法中,img变量代表getImage()方法返回的图像。
getImage()方法以getParameter()方法返回的字符串为图像的文件名。
这个图像可从与getDocumentBase()的调用结果有关的URL中下载,这也是小应用程序所在的HTML页面的URL。
方法getParameter("img")返回的文件名来自于小应用程序标记。
除了速度稍慢以外,这与使用HTML标记是等效的。
图23-1表示了运行程序所看到的结果。
图23-1SimpleImageLoad程序的输出
当这个小应用程序运行时,它在init()方法中启动加载img。
在屏幕上你能看到正从网络上下载的图像,因为每当更多的图像数据到达时,Applet对ImageObserver接口的实现就会调用paint()方法。
用这种方法,只要图像被完整的加载,就能简单的在屏幕上立即显现。
你可以一边用其他信息在屏幕上画图,同时用ImageObserver(我们接下来将要讨论)来监控图像的加载。
如果你利用加载图像的时间去并行完成其他的事情,可能会更好。
23.3ImageObserver
ImageObserver是一个接口,当图像被生成时用来接收消息。
ImageObserver仅定义了一种方法:
imageUpdate()。
用一个图像监视器能允许你完成其他的操作,例如在你被告知下载进程时,显示进程消息或一个吸引人的屏幕。
在图像正通过网络加载时,这种消息非常有用,在这种情况下,人们常常试图通过一个很慢的调制解调器来加载图像决不是好办法。
ImageUpdate()方法有以下所示的一般形式:
booleanimageUpdate(ImageimgObj,intflags,intleft,inttop,intwidth,intheight)
这里,参数imgObj是被加载的图像,参数flags是表示更新状况的整数。
四个整数,left,top,width和height代表一个矩形,它根据flags中所传递的不同值包含不同的数值。
如果ImageUpdate()方法完成了加载,它将返回false,如果还有图像要处理,将返回true。
flags参数包含一个或更多的在ImageObserver接口中定义为静态变量的位标识。
这些标识以及它们所提供的信息如表23-1中所列。
表23-1ImageUpdate()中定义的位标识
标识
意义
WIDTH
Width这个标识是很有用的,它表示了图像的宽度
HEIGHT
Height这个标识是很有用的,它表示了图像的高度
PROPERTIES
与图像相关的属性可以通过使用imgObg.getProperty()获得
SOMEBITS
用于画图的像素已经收到。
参数left,right,width和height定义了包含新像素的矩形
FRAMEBITS
以前所画的一个多帧图像中的完整一帧已经收到。
这个帧可以被显示。
参数left,right,width和height没有使用
ALLBITS
图像已经被完成。
参数left,right,width和height没有使用
ERROR
在异步跟踪一幅图像时发生了一个错误。
图像未完成,不能显示。
没有获得更多的图像信息。
ABORT被设置,表示图像生成被异常中止
ABORT
在图像完成之前,一幅异步跟踪的图像异常中止。
然而,如果没有发生错误,访问图像的任何数据都将重新开始生成图像
Applet类有一个为ImageObserver接口所实现的方法imageUpdate(),用来重画被加载的图像。
你可以在你的类中覆盖此方法以改变它的操作。
这里是imageUpdate()的一个简单的例子:
publicbooleanimageUpdate(Imageimg,intflags,
intx,inty,intw,inth){
if((flags&ALLBITS)==0){
System.out.println("Stillprocessingtheimage.");
returntrue;
}else{
System.out.println("Doneprocessingtheimage.");
returnfalse;
}
}
23.3.1ImageObserver示例
现在我们看一个实例,它重载imageUpdate(),生成SimpleImageLoad小应用程序的一个版本,它不会使屏幕闪烁的那么频繁。
Applet中ImageUpdate()的默认的实现有几处问题。
首先,每次新数据到来时它都要重画整个图像。
这就造成了背景颜色与图像之间的闪动。
其次,它使用了Applet.repaint()以使系统每十分之一秒左右重画图像一次。
这就造成了一种急促,不均匀的感觉,图像好像是油画。
最后,默认的实现对可能正常加载失败的图像一无所知。
getImage()方法即使当设定图像并不存在时也能成功,许多Java程序员一开始会因此遭到失败。
在imageUpdate()出现之前,你发现不了缺少图像。
如果你使用imageUpdate()的默认实现,那么你决不会知道发生了什么。
你的paint()方法在你调用g.drawImage时将什么也不作。
下面的例子改正了SimpleImageLoad程序代码中的所有三个问题。
首先,它通过两个小的改动消除了闪动。
它重载了update()方法,这样它能调用paint()方法而无须首先画背景颜色。
背景通过init()方法中的setBackground()方法来设定,这样,初始颜色只需画一次。
除此以外,它还使用了repaint()方法的一个实现,来设定一个矩形以确定需要重画的范围。
系统将设定剪下的区域,这样矩形以外的部分都无需被重画。
这就减少了重画引起的闪烁,改善了性能。
其次,它消除了对引入图像的急促的,不均匀的显示。
其方法是在每次接到更新时都进行画图。
这些更新是在逐行扫描的基础上进行,因此一个100像素高的图像将在被加载时被重画100次。
注意这不是显示图像的最快的方法,仅是显示最平滑的方法。
最后,它控制了由于找不到想要的文件而造成的错误,其方法是为ABORT位检查flags参数。
如果它被设定,那么,实例变量error就被设为true,接着就调用repaint()。
修改paint()方法,从而在error为true时能够打印出一条以淡红色为背景的出错信息。
程序代码如下所示:
/*
*
*
*
*/
importjava.awt.*;
importjava.applet.*;
publicclassObservedImageLoadextendsApplet{
Imageimg;
booleanerror=false;
Stringimgname;
publicvoidinit(){
setBackground(Color.blue);
imgname=getParameter("img");
img=getImage(getDocumentBase(),imgname);
}
publicvoidpaint(Graphicsg){
if(error){
Dimensiond=getSize();
g.setColor(Color.red);
g.fillRect(0,0,d.width,d.height);
g.setColor(Color.black);
g.drawString("Imagenotfound:
"+imgname,10,d.height/2);
}else{
g.drawImage(img,0,0,this);
}
}
publicvoidupdate(Graphicsg){
paint(g);
}
publicbooleanimageUpdate(Imageimg,intflags,
intx,inty,intw,inth){
if((flags&SOMEBITS)!
=0){//newpartialdata
repaint(x,y,w,h);//paintnewpixels
}elseif((flags&ABORT)!
=0){
error=true;//filenotfound
repaint();//paintwholeapplet
}
return(flags&(ALLBITS|ABORT))==0;
}
}
图23-2给出了小应用程序运行时的两个单独的屏幕。
上面的屏幕显示了被装载了一半的图像,下面的屏幕显示了在小应用程序中被敲错的文件名。
图23-2ObservedImageLoad程序的输出
下面是imageUpdate( )的另一种实现。
它直到图像完全被装入后,才重画到屏幕上。
publicbooleanimageUpdate(Imageimg,intflags,
intx,inty,intw,inth){
if((flags&ALLBITS)!
=0){
repaint();
}elseif((flags&(ABORT|ERROR))!
=0){
error=true;//filenotfound
repaint();
}
return(flags&(ALLBITS|ABORT|ERROR))==0;
}
23.4
双缓冲
图像不仅如我们刚才所示的能够存储图画,你还能用它们作为屏幕外的图形环境。
这就允许你将任何包含文字和图形的图像传递给一个屏幕外的缓冲区,这样就可以在稍后的时间里显示它。
这样做的好处是,图像可以只在它被完成时才被看到。
画一个复杂的图像可能需要几毫秒或更多的时间,在用户看来可能产生闪烁。
这种闪烁使用户感到传递的图像要比实际上慢。
使用画面外图像来减少闪动的方法叫做双缓冲,因为屏幕被看作像素的一个缓冲区,而屏幕外的图像被认为是第二个缓冲区,可以在那儿准备要显示的像素。
在本章前面,你看到了如何去生成一个空白的Image对象。
现在你将会看到如何在这个图像上而不是在屏幕上画图。
回想前几章我们曾讲过,这需要一个Graphics对象来使用任何一个Java的绘制方法。
一种简易的方法是,你在一个图像上画图时所需要的Graphics对象可以通过getGraphics()方法来获得。
这里是用来产生一个新图像的程序代码段,它能获得它的图片的上下文关系,并用红色像素填充整个图像。
Canvasc=newCanvas();
Imagetest=c.createImage(200,100);
Graphicsgc=test.getGraphics();
gc.setColor(Color.red);
gc.fillRect(0,0,200,100);
一旦创建并填充了一个非屏幕图像,它是不可见的。
为了真正显示这个图像,需调用drawImage()方法。
下面示例绘制了一幅耗时的图像,以此证明在用户可察觉的画图时间里,使用双缓冲会使效果有所不同。
/*
*
*
*/
importjava.awt.*;
importjava.awt.event.*;
importjava.applet.*;
publicclassDoubleBufferextendsApplet{
intgap=3;
intmx,my;
booleanflicker=true;
Imagebuffer=null;
intw,h;
publicvoidinit(){
Dimensiond=getSize();
w=d.width;
h=d.height;
buffer=createImage(w,h);
addMouseMotionListener(newMouseMotionAdapter(){
publicvoidmouseDragged(MouseEventme){
mx=me.getX();
my=me.getY();
flicker=false;
repaint();
}
publicvoidmouseMoved(MouseEventme){
mx=me.getX();
my=me.getY();
flicker=true;
repaint();
}
});
}
publicvoidpaint(Graphicsg){
Graphicsscreengc=null;
if(!
flicker){
screengc=g;
g=buffer.getGraphics();
}
g.setColor(Color.blue);
g.fillRect(0,0,w,h);
g.setColor(Color.red);
for(inti=0;ig.drawLine(i,0,w-i,h);
for(inti=0;ig.drawLine(0,i,w,h-i);
g.setColor(Color.black);
g.drawString("Pressmousebuttontodoublebuffer",10,h/2);
g.setColor(Color.yellow);
g.fillOval(mx-gap,my-gap,gap*2+1,gap*2+1);
if(!
flicker){
screengc.drawImage(buffer,0,0,null);
}
}
publicvoidupdate(Graphicsg){
paint(g);
}
}
这个简单的小应用程序有一个复杂的paint方法,它用蓝色填充了背景,之后在上面画了红色的波纹。
它在上面画了黑色的文字并以坐标为mx,my的点为圆心画了一个黄色的圆。
MouseMoved()方法和MouseDragged()方法被重载以跟踪鼠标的位置。
这些方法除了flicker逻辑变量的设定以外都是一样的。
MouseMoved()将flicker设为true,而mouseDragged()将它设为false。
这时调用repaint()方法在两种情况下有相同的效果:
在鼠标移动而无按钮被按下时将flicker设为true,在鼠标移动并有按钮被按下时将flicker设为false。
在paint()方法被调用而flicker被设为true时,当它在屏幕上被执行时,我们可以看到每一个画图操作。
按下鼠标键,paint()方法被调用而flicker被设为false,我们会看到一幅全然不同的图画。
paint()方法将Graphics的引用g与访问在ini()中所创建的画布之外的缓冲图形相交换。
这样所有的画图操作都不可见。
在paint()方法的最后,我们简单的调用drawImage()方法来一次性地显示所有这些画图方法的结果。
注意将null作为第四个参数传递给drawImage()方法是允许的。
这个参数用来传递一个接收图像事件消息的ImageObserver对象。
由于这不是一个从网络数据流产生的图像,我们并不需要消息提示。
图23-3左边的快照就是当按钮没有被按下时,小应用程序窗口看上去的样子。
我们可以看到,当快照被拍下时图像正在被重画的过程中。
右边的快照显示了当按钮被按下时,图像如何通过双缓冲区的办法保持完整和清晰的。
图23-3双缓冲区输出示意图(左图、右图分别是没有采用和采用双缓冲的示意图)
23.5MediaTracker
许多早期的Java开发者发现,当有多媒体图像被加载时,ImageObserver接口太难于理解和掌握。
开发者团体要求发明一种更简单的办法,允许程序员们同步加载他们所有的图像而不必担心imageUpdate()方法。
应此要求,Sun公司在JDK后来的版本中,在