Chinaunix首页 | 论坛 | 博客
  • 博客访问: 182913
  • 博文数量: 21
  • 博客积分: 1395
  • 博客等级: 中尉
  • 技术积分: 321
  • 用 户 组: 普通用户
  • 注册时间: 2005-04-07 13:57
文章分类

全部博文(21)

文章存档

2012年(1)

2011年(4)

2010年(12)

2009年(2)

2008年(2)

我的朋友

分类: Java

2010-12-31 09:53:38

前两节实现了Swing的绘制机制,即在整个GUI应用中所有的绘制时机是怎么产生的,时机产生后又怎么样经过swing框架的基础处理最终进入合适组件开展具体paint。那么每个组件在得到绘制时机时,如何进行绘制?这就从计算机显示世界的历史开始说起了。
在很久很久以前老一辈革命家是通过INT10直接向显示区内存书写显示字节数据,显示芯片会将这些数据按频率生成模拟信号提供显示器进行显示。此后发生了很多方面的进化,一方面是语言上有了c,c++,所以我们不再直接int10,而是调用其API函数;另一方面是绘制的各个方面被封装化,不再直接画点像素,而是逐渐抽象封装了很多绘制工具包,即如果要绘制一个立方体,将有函数调用,提供其顶点位置即可;再一方面则是硬件开发参与进来,为了提高性能,很多软件上的实现,比如当调整角度时一个立方体的显示将发生像素的变化,这个计算被转移到硬件上实现。这些方面的进化是同步展开的:首先说windows为了隔离不同主机的各类厂商不同的显卡其不同的指令约定,提供了GDI统一API供应用程序调用,windows底层将利用各类厂商的驱动来完成映射,此后windows在.net框架上以GDI+的形式包装了GDI以提供OO的调用形式;于此同时,面对那些对绘制性能要求非常高的应用程序,尤其是3D游戏,为了能够让开发的程序更加迅速地绘制,windows提供了Direct API工具包,其调用绕过了许多环节,迅速操作显卡的显示内存区来完成绘制。Direct因其(for speed)使命决定了此后的由软件设计商来主导硬件开发商设计的历史。随着3D绘制需求日益膨胀,direct不断升级版本,提供了很多更高级的调用函数封装来简化开发,这不仅仅是可调用函数的数量增长,而且是提出了适应开发需求的新的绘制体系。只有全面支持direct的硬件厂商才有更好的出路,于是他们纷纷追寻direct的基调进行硬件设计以实现direct显示加速。到了现代,在windows体系里,如果要开发一般的GUI,则GDI/GDI+是合适的选择,虽然GDI只有一部分可能借助硬件加速,GDI+则完全没有硬件加速,但是应付一般的GUI绰绰有余,关键这是普适任何环境的;如果要开发3D游戏或其他图形应用,则direct是合适的选择,虽然如果主机没有direct显卡将可能无法运行,但是现在的显卡绝大多数都支持direct;而最近microsoft在.net3推出的WPF,则是利用direct3d封装及xml描述来提供统一绘制API。在之前提到的windows启动桌面窗口管理器(Desktop Window Manager,DWM)技术后java2d将默认不使用双缓冲,因为DWM自身实现了窗口双缓冲机制,这个dwm就是建立在WPF基础之上。
Java的想法是在各操作系统上封装其底层图形API提供一个统一的图形绘制工具包,不管是2d还是3d,最终利用java绘制时将把调用转换为操作系统的底层图形进行调用。在Windows平台下要进行绘制,首先要打开一个窗口,而后的底层绘制api调用都要跟这个窗口相关(以窗口作为调用的一个参数或者内置为一个成员变量,从而可以在操作系统层面控制各个应用程序的统一表现,比如防止某个应用程序去绘制另外一个应用程序的窗口),这个设计原则适用于各类窗口操作系统。那么在java swing应用程序中,当打开顶层容器时,一个wwindowpeer窗口对等体被构造,这就对应了底层的一个window窗口,在peer构造方法中将会对此peer作为构造参数包出一个顶层容器的SurfaceData屏面成员对象。此后当绘制时机到来,经框架层层过滤确定要进行本容器内任何一个组件的paint时,框架会追溯组件的祖宗树,最终回到该顶层容器里safelyGetGraphics,这里将会构造一个new Graphics对象,同时将此顶层容器的SurfaceData屏面作为该Graphics对象的构造参数。经过以上的设计,组件的paint中使用safelyGetGraphics过来的Graphics开展的各种draw,都可在具体落实到屏幕时找到对应的底层窗口从而有效地转换成底层windows绘制api完成绘制。
Graphics2D是Graphics的加强版实现,利用它可以做更多的2D绘制,各组件实际上利用Graphics2D开展各种draw。使用Graphics2D的方式是面向oo的,相当直白。

Graphics2D的使用过程即是根据需要预先调整几个成员对象(策略模式),Stroke/Font/Transformation /RendingHint /Clip/Paint/CompositeRule,然后调用fill/draw/drawstring/drawImage等方法时,即可将指定的几何图形,字符,图像按照预调整的策略,生成像素绘制在屏面上。如:
paint(Graphics g){
Graphics2D g2d = (Graphics2D) g;
g2d.setPaint(new Color(50,50,50,100));
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
g2d.setComposite(AlphaComposite.SrcOver);
g2d.setFont(new Font("romon",0,40));
g2d.drawString("abc", 20, 20);

但是,在swing里从顶层容器得到Graphics并传递给该组件进行绘制之前,事情变得有点复杂,主要还是因为双缓冲的故事,现在继续上一节展开分析。
g = safelyGetGraphics(paintingComponent, c);//通过顶层容器c得到paintingComponent的graph环境。
try {
if (hasBuffer) {//如果使用双缓冲,交给RepaintManager对进行绘制,要求考虑从bufferedComponent开始缓冲支持。
RepaintManager rm = RepaintManager.currentManager(
bufferedComponent);
rm.beginPaint();
try {
rm.paint(paintingComponent, bufferedComponent, g,
paintImmediatelyClip.x,
paintImmediatelyClip.y,
paintImmediatelyClip.width,
paintImmediatelyClip.height);
} finally {
rm.endPaint();
}
}
else {//否则直接对paintingComponent绘制。
g.setClip(paintImmediatelyClip.x,paintImmediatelyClip.y,
paintImmediatelyClip.width,paintImmediatelyClip.height);
paintingComponent.paint(g);
}
} finally {
g.dispose();
}
按java的想法,在这里首先通过顶层容器获得Graphics,之后就可以传给Component来使用,内部将把Graphics的各类调用转换为windows底层绘制api(GDI)实现绘制,最后处理掉Graphics(底层释放资源)。首先分析没有双缓冲的情况。

static Graphics safelyGetGraphics(Component c, Component root) {
synchronized(componentObtainingGraphicsFromLock) {
componentObtainingGraphicsFrom = root;
Graphics g = c.getGraphics();
componentObtainingGraphicsFrom = null;
return g;
}
}
Class Component{
public Graphics getGraphics() {
if (peer instanceof LightweightPeer) {
// This is for a lightweight component, need to
// translate coordinate spaces and clip relative
// to the parent.
if (parent == null) return null;
Graphics g = parent.getGraphics();
if (g == null) return null;
if (g instanceof ConstrainableGraphics) {
((ConstrainableGraphics) g).constrain(x, y, width, height);
} else {
g.translate(x,y);//坐标系平移
g.setClip(0, 0, width, height);//裁剪
}
g.setFont(getFont());//字体
return g;
} else {
ComponentPeer peer = this.peer;
return (peer != null) ? peer.getGraphics() : null;
}
}}
Class WComponentPeer{
public Graphics getGraphics() {
SurfaceData surfaceData = this.surfaceData;
if (!isDisposed() && surfaceData != null) {
/* Fix for bug 4746122. Color and Font shouldn't be null */
Color bgColor = background;//顶层容器背景色或配置
if (bgColor == null) {
bgColor = SystemColor.window;
}
Color fgColor = foreground;
if (fgColor == null) {//顶层容器前景色或配置
fgColor = SystemColor.windowText;
}
Font font = this.font;//顶层容器字体或配置
if (font == null) {
font = defaultFont;
}
ScreenUpdateManager mgr =
ScreenUpdateManager.getInstance();
return mgr.createGraphics(surfaceData, this, fgColor,
bgColor, font);
}
return null;
}
}
因为Graphics对应的是顶层容器的窗口的屏面,所以需要根据该组件的祖宗树不断地递归translate,setClip,即调整坐标系和圈定范围。并且对字体,背景色和前景色都会在此过程中记录默认的设置。当该safelyGetGraphics返回时,再一次根据脏区进行setClip,这样传入组件的Graphics即已经基本调配好绘制策略,可以用来绘制了。
public void paint(Graphics g) {
boolean shouldClearPaintFlags = false;

if ((getWidth() <= 0) || (getHeight() <= 0)) {//检查此时的大小范围
return;
}

Graphics componentGraphics = getComponentGraphics(g);//对g设置本组件的字体和前景色,如果没有指定使用祖宗的设置。
Graphics co = componentGraphics.create();//这里将克隆一个Graphics,克隆的目的是某个函数体内对Graphics的内置对象的预置策略不会影响到其他。
try {
RepaintManager repaintManager = RepaintManager.currentManager(this);
Rectangle clipRect = co.getClipBounds();
int clipX;
int clipY;
int clipW;
int clipH;
if (clipRect == null) {
clipX = clipY = 0;
clipW = getWidth();
clipH = getHeight();
}
else {
clipX = clipRect.x;
clipY = clipRect.y;
clipW = clipRect.width;
clipH = clipRect.height;
}

if(clipW > getWidth()) {//再次调整绘制范围
clipW = getWidth();
}
if(clipH > getHeight()) {
clipH = getHeight();
}

if(getParent() != null && !(getParent() instanceof JComponent)) {
adjustPaintFlags();
shouldClearPaintFlags = true;
}

int bw,bh;
boolean printing = getFlag(IS_PRINTING);
if(!printing && repaintManager.isDoubleBufferingEnabled() &&
!getFlag(ANCESTOR_USING_BUFFER) && isDoubleBuffered()) {//双缓冲处理
repaintManager.beginPaint();
try {
repaintManager.paint(this, this, co, clipX, clipY, clipW,
clipH);
} finally {
repaintManager.endPaint();
}
}
else {//实施绘制
// Will ocassionaly happen in 1.2, especially when printing.
if (clipRect == null) {
co.setClip(clipX, clipY, clipW, clipH);
}

if (!rectangleIsObscured(clipX,clipY,clipW,clipH)) {//判断这块区域是否被opaque子组件完全遮挡,如果是则不需要绘制自身,直接绘制涉及的子组件,这里只检查直系子孙。
if (!printing) {//如果当前不是打印状态,执行绘制。
paintComponent(co);
paintBorder(co);//这里插一句,border是画在整个组件画布范围里面的一圈;当对组件getInsets()时得到的就是border的属性。
}
else {
printComponent(co);
printBorder(co);
}
}
if (!printing) {
paintChildren(co);
}
else {
printChildren(co);
}
}
} finally {
co.dispose();//释放克隆体—这代表了使用Graphics的一个模式,在函数体内将参数graphics先克隆出来,然后使用,finally释放掉克隆体,这样退出函数后不会影响到原Graphics的使用。
if(shouldClearPaintFlags) {
setFlag(ANCESTOR_USING_BUFFER,false);
setFlag(IS_PAINTING_TILE,false);
setFlag(IS_PRINTING,false);
setFlag(IS_PRINTING_ALL,false);
}
}
}
protected void paintComponent(Graphics g) {
if (ui != null) {
Graphics scratchGraphics = (g == null) ? null : g.create();//克隆手法
try {
ui.update(scratchGraphics, this);//委托给ui进行具体绘制。
}
finally {
scratchGraphics.dispose();
}
}
}
protected void paintBorder(Graphics g) {
Border border = getBorder();//没有克隆手法,所以如果需要改变g,则需要自己在paintBorder里克隆。
if (border != null) {
border.paintBorder(this, g, 0, 0, getWidth(), getHeight());
}
}

protected void paintChildren(Graphics g) {
boolean isJComponent;
Graphics sg = g;

synchronized(getTreeLock()) {
int i = getComponentCount() - 1;//如果没有子组件直接返回
if (i < 0) {
return;
}
// If we are only to paint to a specific child, determine
// its index.
if (paintingChild != null &&
(paintingChild instanceof JComponent) &&
((JComponent)paintingChild).isOpaque()) {//如果指定了重画子组件(见纪要二),则意味着从该子组件开始往前都可能绘制
for (; i >= 0; i--) {
if (getComponent(i) == paintingChild){
break;
}
}
}
Rectangle tmpRect = fetchRectangle();
boolean checkSiblings = (!isOptimizedDrawingEnabled() &&
checkIfChildObscuredBySibling());//如果不能保证子组件不会相互遮挡并且要求检查
Rectangle clipBounds = null;
if (checkSiblings) {
clipBounds = sg.getClipBounds();//得到剪裁区
if (clipBounds == null) {
clipBounds = new Rectangle(0, 0, getWidth(),
getHeight());
}
}
boolean printing = getFlag(IS_PRINTING);
for (; i >= 0 ; i--) {//对每个子组件进行遍历
Component comp = getComponent(i);
isJComponent = (comp instanceof JComponent);
if (comp != null &&
(isJComponent || isLightweightComponent(comp)) &&
(comp.isVisible() == true)) {//如果是轻量级组件且visible
Rectangle cr;

cr = comp.getBounds(tmpRect);

boolean hitClip = g.hitClip(cr.x, cr.y, cr.width,
cr.height);//检查该子组件是否和要绘制的区域相交

if (hitClip) {//如果相交需要绘制
if (checkSiblings && i > 0) {//如果不是最后一个子组件且要求检查相互遮挡的情况
int x = cr.x;
int y = cr.y;
int width = cr.width;
int height = cr.height;
SwingUtilities.computeIntersection
(clipBounds.x, clipBounds.y,
clipBounds.width, clipBounds.height, cr);

if(getObscuredState(i, cr.x, cr.y, cr.width,
cr.height) == COMPLETELY_OBSCURED) {//检查发现相交区被其他组件遮挡则不必绘制该子组件
continue;
}
cr.x = x;
cr.y = y;
cr.width = width;
cr.height = height;
}
Graphics cg = sg.create(cr.x, cr.y, cr.width,
cr.height);//克隆手法
cg.setColor(comp.getForeground());//设置前景色
cg.setFont(comp.getFont());//设置字体
boolean shouldSetFlagBack = false;//下文调整标志位后即开始绘制。
try {
if(isJComponent) {
if(getFlag(ANCESTOR_USING_BUFFER)) {
((JComponent)comp).setFlag(
ANCESTOR_USING_BUFFER,true);
shouldSetFlagBack = true;
}
if(getFlag(IS_PAINTING_TILE)) {
((JComponent)comp).setFlag(
IS_PAINTING_TILE,true);
shouldSetFlagBack = true;
}
if(!printing) {
((JComponent)comp).paint(cg);
}
else {
if (!getFlag(IS_PRINTING_ALL)) {
comp.print(cg);//
}
else {
comp.printAll(cg);
}
}
} else {
if (!printing) {
comp.paint(cg);
}
else {
if (!getFlag(IS_PRINTING_ALL)) {
comp.print(cg);
}
else {
comp.printAll(cg);
}
}
}
} finally {
cg.dispose();
if(shouldSetFlagBack) {
((JComponent)comp).setFlag(
ANCESTOR_USING_BUFFER,false);
((JComponent)comp).setFlag(
IS_PAINTING_TILE,false);
}
}
}
}

}
recycleRectangle(tmpRect);
}
}
以上的绘制长话短说,是因为swing的容器概念,使得我们对某一个组件的绘制总是要包含其子组件的绘制,所以需要根据待绘制区和子组件的情况不断进行筛选绘制。毫无疑问,这种遍历也是一个比较大的开销,尤其是组件层次比较多的情况下。在具体绘制时swing委托给ui来进行具体的java2d绘制,以上的分析graphics2d不管是不是克隆的手法,都是代表实际屏面的,即没有使用双缓冲机制,下文分析双缓冲的情况。
双缓冲将分为两种情况,一种是每窗口双缓冲的策略,一种直接双缓冲。每窗口双缓冲时RepaintManager将使用BufferStrategyPaintManager,此时beginPaint将达到一个同步效果,即如果在awt-windows中正在底层show,则此时的paint将等待。
BufferStrategyPaintManager{
public void beginPaint() {
synchronized(this) {
painting = true;
// Make sure another thread isn't attempting to show from
// the back buffer.
while(showing) {
try {
wait();
} catch (InterruptedException ie) {
}
}
}
这也是beginPaint/endPaint的主要目的。对于非每窗口双缓冲,beginPaint/endPaint组合对是没有用处的。
先来看普通的双缓冲:
Class RepaintManager{
void paint(JComponent paintingComponent,
JComponent bufferComponent, Graphics g,
int x, int y, int w, int h) {
PaintManager paintManager = getPaintManager();
if (!isPaintingThread()) {
// We're painting to two threads at once. PaintManager deals
// with this a bit better than BufferStrategyPaintManager, use
// it to avoid possible exceptions/corruption.
if (paintManager.getClass() != PaintManager.class) {
paintManager = new PaintManager();
paintManager.repaintManager = this;
}
}
if (!paintManager.paint(paintingComponent, bufferComponent, g,
x, y, w, h)) {//委托给合适的PaintManager进行绘制
g.setClip(x, y, w, h);//如果绘制失败,则将直接绘制到屏面上。
paintingComponent.paintToOffscreen(g, x, y, w, h, x + w, y + h);//就像注释里说的,这个方法只在这种极端情况下被调用,其执行逻辑和非缓冲的类似但更粗糙些。
}
}
/**
* Paints to the specified graphics. This does not set the clip and it
* does not adjust the Graphics in anyway, callers must do that first.
* This method is package-private for RepaintManager.PaintManager and
* its subclasses to call, it is NOT intended for general use outside
* of that.
*/
void paintToOffscreen(Graphics g, int x, int y, int w, int h, int maxX,
int maxY) {
try {
setFlag(ANCESTOR_USING_BUFFER, true);
if ((y + h) < maxY || (x + w) < maxX) {
setFlag(IS_PAINTING_TILE, true);
}
if (getFlag(IS_REPAINTING)) {
// Called from paintImmediately (RepaintManager) to fill
// repaint request
paint(g);
} else {
// Called from paint() (AWT) to repair damage
if(!rectangleIsObscured(x, y, w, h)) {
paintComponent(g);
paintBorder(g);
}
paintChildren(g);
}
} finally {
setFlag(ANCESTOR_USING_BUFFER, false);
setFlag(IS_PAINTING_TILE, false);
}
}
Class PaintManager{
public boolean paint(JComponent paintingComponent,
JComponent bufferComponent, Graphics g,
int x, int y, int w, int h) {
// First attempt to use VolatileImage buffer for performance.
// If this fails (which should rarely occur), fallback to a
// standard Image buffer.
//Graphics总是尽力先获得一个VolatileImage,因为该Image得到了性能优化,比如它可能直接会在显示内存区分配有对//应空间,但是正因为此,该Image是受系统资源限制而易变的,因此使用起来需要验证有效性。
boolean paintCompleted = false;
Image offscreen;
if (repaintManager.useVolatileDoubleBuffer() &&
(offscreen = getValidImage(repaintManager.
getVolatileOffscreenBuffer(bufferComponent, w, h))) != null) {
VolatileImage vImage = (java.awt.image.VolatileImage)offscreen;
GraphicsConfiguration gc = bufferComponent.
getGraphicsConfiguration();
for (int i = 0; !paintCompleted &&
i < RepaintManager.VOLATILE_LOOP_MAX; i++) {//在此参数范围内swing总是试图用VolatileImage完成绘制
if (vImage.validate(gc) ==
VolatileImage.IMAGE_INCOMPATIBLE) {//验证vImage的可用性
repaintManager.resetVolatileDoubleBuffer(gc);
offscreen = repaintManager.getVolatileOffscreenBuffer(
bufferComponent,w, h);
vImage = (java.awt.image.VolatileImage)offscreen;
}
paintDoubleBuffered(paintingComponent, vImage, g, x, y,
w, h);//具体的绘制
paintCompleted = !vImage.contentsLost();//检查自上次验证后的绘制是否生效
}
}
// VolatileImage painting loop failed, fallback to regular
// offscreen buffer
if (!paintCompleted && (offscreen = getValidImage(
repaintManager.getOffscreenBuffer(
bufferComponent, w, h))) != null) {//创建普通Image
paintDoubleBuffered(paintingComponent, offscreen, g, x, y, w,
h);
paintCompleted = true;
}
return paintCompleted;
}
protected void paintDoubleBuffered(JComponent c, Image image,
Graphics g, int clipX, int clipY,
int clipW, int clipH) {
Graphics osg = image.getGraphics();//从image中获得Graphics对象
int bw = Math.min(clipW, image.getWidth(null));
int bh = Math.min(clipH, image.getHeight(null));
int x,y,maxx,maxy;

try {
for(x = clipX, maxx = clipX+clipW; x < maxx ; x += bw ) {
for(y=clipY, maxy = clipY + clipH; y < maxy ; y += bh) {
osg.translate(-x, -y);
osg.setClip(x,y,bw,bh);
c.paintToOffscreen(osg, x, y, bw, bh, maxx, maxy);//以该image为屏面进行绘制
g.setClip(x, y, bw, bh);
g.drawImage(image, x, y, c);//将image绘制到真正的屏面上。
osg.translate(x, y);
}
}
} finally {
osg.dispose();
}
}
双缓冲下的实现长话短说就是利用java2d中对Image的处理支持。通过在内存中构造一个Image,这就代表了一个可绘制的屏面,可以此为屏面从而获得Graphics,而后让组件的绘制使用此Graphics对象。当组件完成绘制后,仅仅是改变了该Image。最后由swing将Image一次性画到真正的窗口屏面上去。为了提高性能,java2d提供了volatileImage,并且各Image的创建时都从相应的GraphicsConfiguration创建以保证colormodal等兼容性从而可以快速将Image绘制到真正的屏面上。
对每窗口双缓冲的策略,绘制则是要针对每个窗口对应的缓冲Image进行。
阅读(1162) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2010-12-31 19:06:01

很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com