- 浏览: 55333 次
- 性别:
- 来自: 北京
最新评论
-
fjjiaboming:
求指教.求指教.
JAVA InputMethod 输入法实现纪要 -
fjjiaboming:
能不能详细点??????????想写一写输入法...
JAVA InputMethod 输入法实现纪要 -
mayi140611:
高深,学习中,
Java dnd拖拽实现分析纪要
JAVA Painting-Swing实现纪要二
然后继续< JAVA Painting-Swing实现纪要一>by netbaixc。
纪要一已经分析了swing paint机制的一个大概的轮廓。这里将主要分析RepatinManager是如何维护绘制请求,又如何执行绘制的。
Componet类提供了几个重载repaint,
public void repaint();
public void repaint(long tm);
public void repaint(int x, int y, int width, int height);
JComponent类提供了一个重载repaint
public void repaint(Rectangle r);
这些repaint都将补充一些参数后去调用
Class JComponent.public void repaint(long tm, int x, int y, int width, int height){
RepaintManager.currentManager(this).addDirtyRegion(this, x, y, width, height);}
Class Component. public void repaint(long tm, int x, int y, int width, int height){
}
对于轻量级swing组件自然就是JComponent的repaint方法,对于顶层容器本身已经提供一个重载的public void repaint(long time, int x, int y, int width, int height)。
总之,对于swing的组件,除了纪要一分析的底层expose双缓冲flip的情况外,都是通过RepaintManager. addDirtyRegion来请求绘制。
/**
* Add a component in the list of components that should be refreshed.
* If <i>c</i> already has a dirty region, the rectangle <i>(x,y,w,h)</i>
* will be unioned with the region that should be redrawn.
*
* @see JComponent#repaint
*/
//addDirtyRegion调用该方法
private void addDirtyRegion0(Container c, int x, int y, int w, int h) {
/* Special cases we don't have to bother with.
*/
if ((w <= 0) || (h <= 0) || (c == null)) {//以防这种没有意义的请求
return;
}
if ((c.getWidth() <= 0) || (c.getHeight() <= 0)) {//以防这种没有意义的请求
return;
}
if (extendDirtyRegion(c, x, y, w, h)){ //在接受这次请求的时候,可能还有一些请求没有被调度处理,
//要考虑过去的请求中是否有同组件的请求,如果有,直接合并
//即可,合并成功即可返回。
// Component was already marked as dirty, region has been
// extended, no need to continue.
return;
}
//看来没有合并成功,则需要将此请求作为新请求处理
/* Make sure that c and all it ancestors (up to an Applet or
* Window) are visible. This loop has the same effect as
* checking c.isShowing() (and note that it's still possible
* that c is completely obscured by an opaque ancestor in
* the specified rectangle).
*/
Component root = null;
// Note: We can't synchronize around this, Frame.getExtendedState
// is synchronized so that if we were to synchronize around this
// it could lead to the possibility of getting locks out
// of order and deadlocking.
for (Container p = c; p != null; p = p.getParent()) {
if (!p.isVisible() || (p.getPeer() == null)) {
return;
}//首先校验整个层次是visible的,否则是没意义的请求。-这里我怀疑是一个逻辑BUG,就是前面合并请求的//时候为什么不去判断呢?如果visible被置为了false,那么当前这个请求就是应该完全抛弃的,如果合并进去可能就扩展//要重画的脏区,不合逻辑了。
if ((p instanceof Window) || (p instanceof Applet)) {
// Iconified frames are still visible!
if (p instanceof Frame &&
(((Frame)p).getExtendedState() & Frame.ICONIFIED) ==
Frame.ICONIFIED) {
return;//检查顶层容器是否处于最小化状态,如果是最小化状态,也是无意义的请求。
}
root = p;
break;
}
}
if (root == null) return;//搞到最后,顶层容器竟然是Null,不用搞了也
synchronized(this) {//因为前面没有同步(因害怕死锁考虑),所以在这里小同步下时考察合并,实在不能合并,注册到dirtyComponents-{组件->几何区域}
if (extendDirtyRegion(c, x, y, w, h)) {
// In between last check and this check another thread
// queued up runnable, can bail here.
return;
}
dirtyComponents.put(c, new Rectangle(x, y, w, h));
}
// Queue a Runnable to invoke paintDirtyRegions and
// validateInvalidComponents.
scheduleProcessingRunnable();//post 一个InvocationEvent请求EDT执行绘制过程。
}
/**
* Extends the dirty region for the specified component to include
* the new region.
*
* @return false if <code>c</code> is not yet marked dirty.
*/
private synchronized boolean extendDirtyRegion(
Component c, int x, int y, int w, int h) {
Rectangle r = (Rectangle)dirtyComponents.get(c);//历史的请求
if (r != null) {
// A non-null r implies c is already marked as dirty,
// and that the parent is valid. Therefore we can
// just union the rect and bail.
SwingUtilities.computeUnion(x, y, w, h, r);//合并区域(就是一个矩形求合集的处理,略了)
return true;
}
return false;
}
private void scheduleProcessingRunnable(AppContext context) {
if (processingRunnable.markPending()) {//如果这个processingRunnable单例已经挂在EVENTQUEUE中还没处理,这次就不必再post了。
SunToolkit.getSystemEventQueueImplPP(context).
postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(),
processingRunnable));
}
}
当EDT获取到那个InvocationEvent时将执行processingRunnable这个单例。
private final class ProcessingRunnable implements Runnable {
// If true, we're wainting on the EventQueue.
private boolean pending;
/**
* Marks this processing runnable as pending. If this was not
* already marked as pending, true is returned.
*/
public synchronized boolean markPending() {//mark
if (!pending) {
pending = true;
return true;
}
return false;
}
public void run() {
synchronized (this) {
pending = false;
}
// First pass, flush any heavy paint events into real paint
// events. If there are pending heavy weight requests this will
// result in q'ing this request up one more time. As
// long as no other requests come in between now and the time
// the second one is processed nothing will happen. This is not
// ideal, but the logic needed to suppress the second request is
// more headache than it's worth.
scheduleHeavyWeightPaints();//因为nativeAddDirtyRegion是在AWT-Windows提交,而我们的//repaint应该在EDT中提交,两个线程同步运行,如果共享dirtyComponents,将有线程同步的考虑,(即一边EDT在处理//并清除注册的请求,另一边AWT在不断提交新请求),因此采用两个map记录,在这里进行一次合并。注释里说的看不懂
// Do the actual validation and painting.
validateInvalidComponents();//这涉及到validate,以后详细分析
prePaintDirtyRegions();//进行绘制操作
}
}
void scheduleHeavyWeightPaints() {//将顶层容器的请求加入到dirtyComponents中
Map<Container,Rectangle> hws;
synchronized(this) {
if (hwDirtyComponents.size() == 0){//hwDirtyComponents由下面nativeAddDirtyRegion加入
return;
}
hws = hwDirtyComponents;
hwDirtyComponents = new IdentityHashMap<Container,Rectangle>();
}
for (Container hw : hws.keySet()) {
Rectangle dirty = hws.get(hw);
if (hw instanceof Window) {
addDirtyRegion((Window)hw, dirty.x, dirty.y,
dirty.width, dirty.height);
}
else if (hw instanceof Applet) {
addDirtyRegion((Applet)hw, dirty.x, dirty.y,
dirty.width, dirty.height);
}
else { // SwingHeavyWeight
addDirtyRegion0(hw, dirty.x, dirty.y,
dirty.width, dirty.height);
}
}
}
void nativeAddDirtyRegion(AppContext appContext, Container c,
int x, int y, int w, int h) {//底层处理expose时如果没有双缓冲的处理的调用
if (w > 0 && h > 0) {
synchronized(this) {//就是尽量合并并放在hwDirtyComponents中
Rectangle dirty = hwDirtyComponents.get(c);
if (dirty == null) {
hwDirtyComponents.put(c, new Rectangle(x, y, w, h));
}
else {
hwDirtyComponents.put(c, SwingUtilities.computeUnion(
x, y, w, h, dirty));
}
}
scheduleProcessingRunnable(appContext);
}
}
/**
* This is invoked to process paint requests. It's needed
* for backward compatability in so far as RepaintManager would previously
* not see paint requests for top levels, so, we have to make sure
* a subclass correctly paints any dirty top levels.
*/
private void prePaintDirtyRegions() {
Map<Component,Rectangle> dirtyComponents;
java.util.List<Runnable> runnableList;
synchronized(this) {
dirtyComponents = this.dirtyComponents;
runnableList = this.runnableList;
this.runnableList = null;
}
if (runnableList != null) {
for (Runnable runnable : runnableList) {//D3D有关,以后java3d时分析
runnable.run();
}
}
paintDirtyRegions();//
if (dirtyComponents.size() > 0) {//在jre1.7前都是实现自己的RepaintManager,paintDirtyRegions方法,可以不考虑顶层容器,而现在jre1.7要考虑了,因此这里为了兼容,补充了新方法处理
// This'll only happen if a subclass isn't correctly dealing
// with toplevels.
paintDirtyRegions(dirtyComponents);
}
}
/**
* Paint all of the components that have been marked dirty.
*
* @see #addDirtyRegion
*/
public void paintDirtyRegions() {
synchronized(this) { // swap for thread safety
Map<Component,Rectangle> tmp = tmpDirtyComponents;
tmpDirtyComponents = dirtyComponents;
dirtyComponents = tmp;
dirtyComponents.clear();
}
//上面跟前面说的顶层容器和一般组件的请求分开注册类似,为了线程同步安全但如果所有的repaint都是在EDT调用,这恐怕就没有意义了。
paintDirtyRegions(tmpDirtyComponents);
}
private void paintDirtyRegions(Map<Component,Rectangle>
tmpDirtyComponents){
int i, count;
java.util.List<Component> roots;
Component dirtyComponent;
count = tmpDirtyComponents.size();
if (count == 0) {
return;
}//如果没有注册的脏区{组件->几何区域},返回
Rectangle rect;
int localBoundsX = 0;
int localBoundsY = 0;
int localBoundsH = 0;
int localBoundsW = 0;
Enumeration keys;
roots = new ArrayList<Component>(count);
for (Component dirty : tmpDirtyComponents.keySet()) {
collectDirtyComponents(tmpDirtyComponents, dirty, roots);//此时的tmpDirtyComponents
//中可能存在脏的祖宗组件,
//将把几何区域合并到祖宗组件//中--roots
}
count = roots.size();//从合并后的脏祖宗组件画起
// System.out.println("roots size is " + count);
painting = true;
try {
for(i=0 ; i < count ; i++) {
dirtyComponent = roots.get(i);
rect = tmpDirtyComponents.get(dirtyComponent);
// System.out.println("Should refresh :" + rect);
localBoundsH = dirtyComponent.getHeight();
localBoundsW = dirtyComponent.getWidth();
SwingUtilities.computeIntersection(localBoundsX,
localBoundsY,
localBoundsW,
localBoundsH,
rect);//确定脏区在该组件的bound范围,其实在合并中已经确定过一次了,这里又一次确定,难道不嫌啰嗦么
if (dirtyComponent instanceof JComponent) {
((JComponent)dirtyComponent).paintImmediately(
rect.x,rect.y,rect.width, rect.height);//轻量级组件在这里开始画这个区域
}
else if (dirtyComponent.isShowing()) {
Graphics g = JComponent.safelyGetGraphics(
dirtyComponent, dirtyComponent);
// If the Graphics goes away, it means someone disposed of
// the window, don't do anything.
if (g != null) {
g.setClip(rect.x, rect.y, rect.width, rect.height);
try {
dirtyComponent.paint(g);//顶层容器的paint
} finally {
g.dispose();
}
}
}
// If the repaintRoot has been set, service it now and
// remove any components that are children of repaintRoot.
if (repaintRoot != null) {//每窗口缓存策略下的支持
adjustRoots(repaintRoot, roots, i + 1);
count = roots.size();
paintManager.isRepaintingRoot = true;
repaintRoot.paintImmediately(0, 0, repaintRoot.getWidth(),
repaintRoot.getHeight());
paintManager.isRepaintingRoot = false;
// Only service repaintRoot once.
repaintRoot = null;
}
}
} finally {
painting = false;
}
tmpDirtyComponents.clear();//清理dirtyComponent
}
上述的代码已经到了具体swing组件的paintImmediately中,此时提供的几个参数分别是该组件要绘制的区域相对本组件的x,y和该区域的width,height。
public void paintImmediately(int x,int y,int w, int h) {
Component c = this;
Component parent;
if(!isShowing()) {//检查一下当前是否显示有效:首先这种检查只是根据contain tree来检查,对于不在一个tree的组件如果完全遮挡则检查不到;另外对于repaint引起的此次调用(非直接调用),这里有个意义是因为绘制是异步的---提交绘制请求和后面的EDT处理该请求,两回都要校验,这里是处理请求执行的第2次校验---这说明应用提交请求后还允许反悔,只要在EDT开始处理请求前反悔都是成功的。
return;
}
while(!((JComponent)c).isOpaque()) {//opaque确定该组件是否完全不透明,如果不是则需要从祖宗画起,这样提交请求是脏A组件,但经过上面合并后可能并入已提交的脏B组件,而在这里可能要转而绘制B组件的祖宗C组件。
parent = c.getParent();
if(parent != null) {
x += c.getX();
y += c.getY();//处理坐标的迁移,使之相对父组件
c = parent;
} else {
break;
}
if(!(c instanceof JComponent)) {
break;
}
}
if(c instanceof JComponent) {
((JComponent)c) _paintImmediately (x,y,w,h);//向上追到一个opaque的轻量级父组件具体绘制(可找到该绘制的目标了)
} else {
c.repaint(x,y,w,h);//没有的话那就是顶层容器的绘制了。
}
}
void __paintImmediately(int x, int y, int w, int h) {
Graphics g;
Container c;
Rectangle b;
int tmpX, tmpY, tmpWidth, tmpHeight;
int offsetX=0,offsetY=0;
boolean hasBuffer = false;
JComponent bufferedComponent = null;
JComponent paintingComponent = this;
RepaintManager repaintManager = RepaintManager.currentManager(this);
// parent Container's up to Window or Applet. First container is
// the direct parent. Note that in testing it was faster to
// alloc a new Vector vs keeping a stack of them around, and gc
// seemed to have a minimal effect on this.
java.util.List<Component> path = new java.util.ArrayList<Component>(7);
int pIndex = -1;
int pCount = 0;
tmpX = tmpY = tmpWidth = tmpHeight = 0;//此后始终tmpX = tmpY==0
Rectangle paintImmediatelyClip = fetchRectangle();//从回收池里获取一个Rectangle
paintImmediatelyClip.x = x;
paintImmediatelyClip.y = y;
paintImmediatelyClip.width = w;
paintImmediatelyClip.height = h;//用来记录要绘制的区域
// System.out.println("1) ************* in _paintImmediately for " + this);
boolean ontop = alwaysOnTop() && isOpaque();//ontop说明了该组件一直在界面最上层不会被非子组件遮挡,如果==true,则下面那个大循环就做一件事情,找到该组件的顶层容器,此后就是从顶层容器拿到Graph2D开始绘制该组件。如果==false,则要考虑该组件有可能被非子组件(兄弟组件)遮挡的情况。
if (ontop) {
SwingUtilities.computeIntersection(0, 0, getWidth(), getHeight(),//我靠,这里又确定一次脏区是否超出组件当前范围
paintImmediatelyClip);
if (paintImmediatelyClip.width == 0) {//经交集判断后宽度为0(或高度为0)则意味着超出范围,回收Rectangle并返回。
recycleRectangle(paintImmediatelyClip);
return;
}
}
Component child;
for (c = this, child = null;
c != null && !(c instanceof Window) && !(c instanceof Applet);
child = c, c = c.getParent()) {
//ontop记录的始终是要绘制的组件的属性;循环将一直向上递推到顶层容器,因为要通过顶层容器拿到Graph2D;同时在这此循环中将要考虑兄弟组件相互遮挡的情况,可能因此重新设置要最终要绘制的组件,也就说这次循环实际遍历做了2件事情。
JComponent jc = (c instanceof JComponent) ? (JComponent)c :
null;//jc始终记录向上的轻量级父组件,如果某一级出现非轻量级父组件则为空,c则始终记录向上的组件,不管轻重。
path.add(c);//path记录向上递推的整个路径
if(!ontop && jc != null && !jc.isOptimizedDrawingEnabled()) {//如果不能确定那个发起绘制的组件一定是ontop,而且如果当前该组件是轻量级组件而且不能保证子组件不重叠,需要考虑那个要求绘制的子组件是否与兄弟组件有遮挡关系,从而确定不同的绘制路径
boolean resetPC;
// Children of c may overlap, three possible cases for the
// painting region:
// . Completely obscured by an opaque sibling, in which
// case there is no need to paint.
// . Partially obscured by a sibling: need to start
// painting from c.
// . Otherwise we aren't obscured and thus don't need to
// start painting from parent.
if (c != this) {//当前组件已经向上循环成要绘制的组件的老一辈,就是说当前已经循环过第一轮了。
if (jc.isPaintingOrigin()) {//如果该组件已经声明所有子组件的绘制请求都应该从本组件绘制起
resetPC = true;//则直接要求重新设置准备绘制的组件为当前组件
}
else {//如果该组件没有要求一定代理子组件的绘制,需要判断要求绘制的那个子组件是否有兄弟组件遮盖
Component[] children = c.getComponents();
int i = 0;
for (; i<children.length; i++) {
if (children[i] == child) break;//得到子组件的索引
}
switch (jc.getObscuredState(i,
paintImmediatelyClip.x,
paintImmediatelyClip.y,
paintImmediatelyClip.width,
paintImmediatelyClip.height)) {//检查前面加入的子组件是否将该子组件遮挡。这个地方绕了我一下!因为我有个错误认识,首先给容器加入子组件时childIndex是从0加起这是没错的,但我感觉是后面加入的总会覆盖前面的,所以这里应该判断后面加入的组件是否将该组件遮挡啊。但是实际上,容器绘制子组件时是从后面向前一直画到0,一测就明白了,是前面加入的后画,因此前面加入的子组件会覆盖后面加入的子组件。Awt为什么要这样倒序绘制?
case NOT_OBSCURED:
resetPC = false;//各不相干则不用考虑
break;
case COMPLETELY_OBSCURED://经判断那个子组件已经被某个前面加入的组件完全遮挡直接返回并忽略绘制请求,因为根据倒序绘制的顺序,现在屏幕上显示的是那个兄弟组件。
recycleRectangle(paintImmediatelyClip);
return;
default:
resetPC = true;//部分包含则要调整绘制组件,从父组件-当前组件画起,因为这样才能把那些涉嫌脏区的兄弟组件也给绘制出来。
break;
}
}
}
else {
resetPC = false;//仍然是当前组件,就是说第一轮循环,不需要下面reset绘制组件
}
if (resetPC) {
// Get rid of any buffer since we draw from here and
// we might draw something larger
paintingComponent = jc;//要绘制的组件转为当前组件
pIndex = pCount;//要绘制的组件相对目标组件的层次
offsetX = offsetY = 0;//通过清零是脏区在最终绘制前变成该当前组件的坐标系。
hasBuffer = false;//如果已经调整了绘制组件,则要重新初始化hasBuffer,因为此时该组件可能不支持双缓冲,而在此之前的循环中存在过bufferComponent父组件导致hasBuffer=true。因为要最终绘制组件时需要得到的参数bufferComponent应该是要绘制组件的父组件,而这里重新设置了要最终绘制的组件但还没对父组件进行遍历,所以需要重新初始化。
}
}
pCount++;//向上循环层数
// look to see if the parent (and therefor this component)
// is double buffered
//记录在向上的路径下最后一个支持双缓冲的父组件,而且只要最终要绘制的组件有支持双缓冲的祖宗组件就会使用双缓冲(hasBuffer==true)
if(repaintManager.isDoubleBufferingEnabled() && jc != null &&
jc.isDoubleBuffered()) {//如果当前组件获得双缓冲支持则需要置双缓冲标志
hasBuffer = true;
bufferedComponent = jc;
}
// if we aren't on top, include the parent's clip
if (!ontop) {//如果不能确定那个发起绘制的组件一定是ontop,需要重新调整脏区的坐标系
int bx = c.getX();
int by = c.getY();
tmpWidth = c.getWidth();
tmpHeight = c.getHeight();
SwingUtilities.computeIntersection(tmpX,tmpY,tmpWidth,tmpHeight,paintImmediatelyClip);
paintImmediatelyClip.x += bx;//使区域相对于当前组件的父组件
paintImmediatelyClip.y += by;
offsetX += bx;
offsetY += by;
}
}
//paintingComponent现在是要经上面循环调整过的要开始绘制的最终的那个组件,
// If the clip width or height is negative, don't bother painting
//此时再次校验是否有效绘制区域,否则忽略并回收区域,真谨慎,真不容易啊
if(c == null || c.getPeer() == null ||
paintImmediatelyClip.width <= 0 ||
paintImmediatelyClip.height <= 0) {
recycleRectangle(paintImmediatelyClip);
return;
}
paintingComponent.setFlag(IS_REPAINTING, true);
//调整paintImmediatelyClip,使其成为相对绘制组件的坐标系
paintImmediatelyClip.x -= offsetX;
paintImmediatelyClip.y -= offsetY;
// Notify the Components that are going to be painted of the
// child component to paint to.
if(paintingComponent != this) {//如果调整过最终绘制的组件,记录该组件的孩子路径,以后有用地
Component comp;
int i = pIndex;
for(; i > 0 ; i--) {
comp = path.get(i);
if(comp instanceof JComponent) {
((JComponent)comp).setPaintingChild(path.get(i-1));
}
}
}
//真正进行绘制
try {
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();
}
}
finally {
// Reset the painting child for the parent components.
if(paintingComponent != this) {//清空记录
Component comp;
int i = pIndex;
for(; i > 0 ; i--) {
comp = path.get(i);
if(comp instanceof JComponent) {
((JComponent)comp).setPaintingChild(null);
}
}
}//恢复标志
paintingComponent.setFlag(IS_REPAINTING, false);
}
recycleRectangle(paintImmediatelyClip);//回收Rectangle
}
上述的这段代码实际上最主要的精神在于,如果要绘制一个组件的某区域,swing需要知道该组件的这块区域是否被兄弟组件们给遮挡。有几个参数可以帮助swing快速判断,没有这几个优化参数的提醒,swing将通过向上遍历到顶层容器的大循环来确定所有可能的遮挡情况(可能该组件没有被兄弟组件遮挡,但其爷爷组件可能被二爷爷给遮挡,这也得要考虑)。注意,如果是多个顶层容器相互遮挡,那这个遍历是没有考虑的,所以可能因此导致没有必要的重画遮挡区。但是因为Graphic2D是从各自顶层容器获取,所以也不会出现穿透的效果。这个遍历无疑是耗费效率的,虽然它已经通过约定容器画子组件的顺序而尽量去减少遍历的节点,也通过多次检查可能的区域不合理来过滤,但是为提高性能还是应该注意如果要自行开发swing组件,要设置优化参数,同时GUI不应该无谓地增加组件层次。
然后继续< JAVA Painting-Swing实现纪要一>by netbaixc。
纪要一已经分析了swing paint机制的一个大概的轮廓。这里将主要分析RepatinManager是如何维护绘制请求,又如何执行绘制的。
Componet类提供了几个重载repaint,
public void repaint();
public void repaint(long tm);
public void repaint(int x, int y, int width, int height);
JComponent类提供了一个重载repaint
public void repaint(Rectangle r);
这些repaint都将补充一些参数后去调用
Class JComponent.public void repaint(long tm, int x, int y, int width, int height){
RepaintManager.currentManager(this).addDirtyRegion(this, x, y, width, height);}
Class Component. public void repaint(long tm, int x, int y, int width, int height){
}
对于轻量级swing组件自然就是JComponent的repaint方法,对于顶层容器本身已经提供一个重载的public void repaint(long time, int x, int y, int width, int height)。
总之,对于swing的组件,除了纪要一分析的底层expose双缓冲flip的情况外,都是通过RepaintManager. addDirtyRegion来请求绘制。
/**
* Add a component in the list of components that should be refreshed.
* If <i>c</i> already has a dirty region, the rectangle <i>(x,y,w,h)</i>
* will be unioned with the region that should be redrawn.
*
* @see JComponent#repaint
*/
//addDirtyRegion调用该方法
private void addDirtyRegion0(Container c, int x, int y, int w, int h) {
/* Special cases we don't have to bother with.
*/
if ((w <= 0) || (h <= 0) || (c == null)) {//以防这种没有意义的请求
return;
}
if ((c.getWidth() <= 0) || (c.getHeight() <= 0)) {//以防这种没有意义的请求
return;
}
if (extendDirtyRegion(c, x, y, w, h)){ //在接受这次请求的时候,可能还有一些请求没有被调度处理,
//要考虑过去的请求中是否有同组件的请求,如果有,直接合并
//即可,合并成功即可返回。
// Component was already marked as dirty, region has been
// extended, no need to continue.
return;
}
//看来没有合并成功,则需要将此请求作为新请求处理
/* Make sure that c and all it ancestors (up to an Applet or
* Window) are visible. This loop has the same effect as
* checking c.isShowing() (and note that it's still possible
* that c is completely obscured by an opaque ancestor in
* the specified rectangle).
*/
Component root = null;
// Note: We can't synchronize around this, Frame.getExtendedState
// is synchronized so that if we were to synchronize around this
// it could lead to the possibility of getting locks out
// of order and deadlocking.
for (Container p = c; p != null; p = p.getParent()) {
if (!p.isVisible() || (p.getPeer() == null)) {
return;
}//首先校验整个层次是visible的,否则是没意义的请求。-这里我怀疑是一个逻辑BUG,就是前面合并请求的//时候为什么不去判断呢?如果visible被置为了false,那么当前这个请求就是应该完全抛弃的,如果合并进去可能就扩展//要重画的脏区,不合逻辑了。
if ((p instanceof Window) || (p instanceof Applet)) {
// Iconified frames are still visible!
if (p instanceof Frame &&
(((Frame)p).getExtendedState() & Frame.ICONIFIED) ==
Frame.ICONIFIED) {
return;//检查顶层容器是否处于最小化状态,如果是最小化状态,也是无意义的请求。
}
root = p;
break;
}
}
if (root == null) return;//搞到最后,顶层容器竟然是Null,不用搞了也
synchronized(this) {//因为前面没有同步(因害怕死锁考虑),所以在这里小同步下时考察合并,实在不能合并,注册到dirtyComponents-{组件->几何区域}
if (extendDirtyRegion(c, x, y, w, h)) {
// In between last check and this check another thread
// queued up runnable, can bail here.
return;
}
dirtyComponents.put(c, new Rectangle(x, y, w, h));
}
// Queue a Runnable to invoke paintDirtyRegions and
// validateInvalidComponents.
scheduleProcessingRunnable();//post 一个InvocationEvent请求EDT执行绘制过程。
}
/**
* Extends the dirty region for the specified component to include
* the new region.
*
* @return false if <code>c</code> is not yet marked dirty.
*/
private synchronized boolean extendDirtyRegion(
Component c, int x, int y, int w, int h) {
Rectangle r = (Rectangle)dirtyComponents.get(c);//历史的请求
if (r != null) {
// A non-null r implies c is already marked as dirty,
// and that the parent is valid. Therefore we can
// just union the rect and bail.
SwingUtilities.computeUnion(x, y, w, h, r);//合并区域(就是一个矩形求合集的处理,略了)
return true;
}
return false;
}
private void scheduleProcessingRunnable(AppContext context) {
if (processingRunnable.markPending()) {//如果这个processingRunnable单例已经挂在EVENTQUEUE中还没处理,这次就不必再post了。
SunToolkit.getSystemEventQueueImplPP(context).
postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(),
processingRunnable));
}
}
当EDT获取到那个InvocationEvent时将执行processingRunnable这个单例。
private final class ProcessingRunnable implements Runnable {
// If true, we're wainting on the EventQueue.
private boolean pending;
/**
* Marks this processing runnable as pending. If this was not
* already marked as pending, true is returned.
*/
public synchronized boolean markPending() {//mark
if (!pending) {
pending = true;
return true;
}
return false;
}
public void run() {
synchronized (this) {
pending = false;
}
// First pass, flush any heavy paint events into real paint
// events. If there are pending heavy weight requests this will
// result in q'ing this request up one more time. As
// long as no other requests come in between now and the time
// the second one is processed nothing will happen. This is not
// ideal, but the logic needed to suppress the second request is
// more headache than it's worth.
scheduleHeavyWeightPaints();//因为nativeAddDirtyRegion是在AWT-Windows提交,而我们的//repaint应该在EDT中提交,两个线程同步运行,如果共享dirtyComponents,将有线程同步的考虑,(即一边EDT在处理//并清除注册的请求,另一边AWT在不断提交新请求),因此采用两个map记录,在这里进行一次合并。注释里说的看不懂
// Do the actual validation and painting.
validateInvalidComponents();//这涉及到validate,以后详细分析
prePaintDirtyRegions();//进行绘制操作
}
}
void scheduleHeavyWeightPaints() {//将顶层容器的请求加入到dirtyComponents中
Map<Container,Rectangle> hws;
synchronized(this) {
if (hwDirtyComponents.size() == 0){//hwDirtyComponents由下面nativeAddDirtyRegion加入
return;
}
hws = hwDirtyComponents;
hwDirtyComponents = new IdentityHashMap<Container,Rectangle>();
}
for (Container hw : hws.keySet()) {
Rectangle dirty = hws.get(hw);
if (hw instanceof Window) {
addDirtyRegion((Window)hw, dirty.x, dirty.y,
dirty.width, dirty.height);
}
else if (hw instanceof Applet) {
addDirtyRegion((Applet)hw, dirty.x, dirty.y,
dirty.width, dirty.height);
}
else { // SwingHeavyWeight
addDirtyRegion0(hw, dirty.x, dirty.y,
dirty.width, dirty.height);
}
}
}
void nativeAddDirtyRegion(AppContext appContext, Container c,
int x, int y, int w, int h) {//底层处理expose时如果没有双缓冲的处理的调用
if (w > 0 && h > 0) {
synchronized(this) {//就是尽量合并并放在hwDirtyComponents中
Rectangle dirty = hwDirtyComponents.get(c);
if (dirty == null) {
hwDirtyComponents.put(c, new Rectangle(x, y, w, h));
}
else {
hwDirtyComponents.put(c, SwingUtilities.computeUnion(
x, y, w, h, dirty));
}
}
scheduleProcessingRunnable(appContext);
}
}
/**
* This is invoked to process paint requests. It's needed
* for backward compatability in so far as RepaintManager would previously
* not see paint requests for top levels, so, we have to make sure
* a subclass correctly paints any dirty top levels.
*/
private void prePaintDirtyRegions() {
Map<Component,Rectangle> dirtyComponents;
java.util.List<Runnable> runnableList;
synchronized(this) {
dirtyComponents = this.dirtyComponents;
runnableList = this.runnableList;
this.runnableList = null;
}
if (runnableList != null) {
for (Runnable runnable : runnableList) {//D3D有关,以后java3d时分析
runnable.run();
}
}
paintDirtyRegions();//
if (dirtyComponents.size() > 0) {//在jre1.7前都是实现自己的RepaintManager,paintDirtyRegions方法,可以不考虑顶层容器,而现在jre1.7要考虑了,因此这里为了兼容,补充了新方法处理
// This'll only happen if a subclass isn't correctly dealing
// with toplevels.
paintDirtyRegions(dirtyComponents);
}
}
/**
* Paint all of the components that have been marked dirty.
*
* @see #addDirtyRegion
*/
public void paintDirtyRegions() {
synchronized(this) { // swap for thread safety
Map<Component,Rectangle> tmp = tmpDirtyComponents;
tmpDirtyComponents = dirtyComponents;
dirtyComponents = tmp;
dirtyComponents.clear();
}
//上面跟前面说的顶层容器和一般组件的请求分开注册类似,为了线程同步安全但如果所有的repaint都是在EDT调用,这恐怕就没有意义了。
paintDirtyRegions(tmpDirtyComponents);
}
private void paintDirtyRegions(Map<Component,Rectangle>
tmpDirtyComponents){
int i, count;
java.util.List<Component> roots;
Component dirtyComponent;
count = tmpDirtyComponents.size();
if (count == 0) {
return;
}//如果没有注册的脏区{组件->几何区域},返回
Rectangle rect;
int localBoundsX = 0;
int localBoundsY = 0;
int localBoundsH = 0;
int localBoundsW = 0;
Enumeration keys;
roots = new ArrayList<Component>(count);
for (Component dirty : tmpDirtyComponents.keySet()) {
collectDirtyComponents(tmpDirtyComponents, dirty, roots);//此时的tmpDirtyComponents
//中可能存在脏的祖宗组件,
//将把几何区域合并到祖宗组件//中--roots
}
count = roots.size();//从合并后的脏祖宗组件画起
// System.out.println("roots size is " + count);
painting = true;
try {
for(i=0 ; i < count ; i++) {
dirtyComponent = roots.get(i);
rect = tmpDirtyComponents.get(dirtyComponent);
// System.out.println("Should refresh :" + rect);
localBoundsH = dirtyComponent.getHeight();
localBoundsW = dirtyComponent.getWidth();
SwingUtilities.computeIntersection(localBoundsX,
localBoundsY,
localBoundsW,
localBoundsH,
rect);//确定脏区在该组件的bound范围,其实在合并中已经确定过一次了,这里又一次确定,难道不嫌啰嗦么
if (dirtyComponent instanceof JComponent) {
((JComponent)dirtyComponent).paintImmediately(
rect.x,rect.y,rect.width, rect.height);//轻量级组件在这里开始画这个区域
}
else if (dirtyComponent.isShowing()) {
Graphics g = JComponent.safelyGetGraphics(
dirtyComponent, dirtyComponent);
// If the Graphics goes away, it means someone disposed of
// the window, don't do anything.
if (g != null) {
g.setClip(rect.x, rect.y, rect.width, rect.height);
try {
dirtyComponent.paint(g);//顶层容器的paint
} finally {
g.dispose();
}
}
}
// If the repaintRoot has been set, service it now and
// remove any components that are children of repaintRoot.
if (repaintRoot != null) {//每窗口缓存策略下的支持
adjustRoots(repaintRoot, roots, i + 1);
count = roots.size();
paintManager.isRepaintingRoot = true;
repaintRoot.paintImmediately(0, 0, repaintRoot.getWidth(),
repaintRoot.getHeight());
paintManager.isRepaintingRoot = false;
// Only service repaintRoot once.
repaintRoot = null;
}
}
} finally {
painting = false;
}
tmpDirtyComponents.clear();//清理dirtyComponent
}
上述的代码已经到了具体swing组件的paintImmediately中,此时提供的几个参数分别是该组件要绘制的区域相对本组件的x,y和该区域的width,height。
public void paintImmediately(int x,int y,int w, int h) {
Component c = this;
Component parent;
if(!isShowing()) {//检查一下当前是否显示有效:首先这种检查只是根据contain tree来检查,对于不在一个tree的组件如果完全遮挡则检查不到;另外对于repaint引起的此次调用(非直接调用),这里有个意义是因为绘制是异步的---提交绘制请求和后面的EDT处理该请求,两回都要校验,这里是处理请求执行的第2次校验---这说明应用提交请求后还允许反悔,只要在EDT开始处理请求前反悔都是成功的。
return;
}
while(!((JComponent)c).isOpaque()) {//opaque确定该组件是否完全不透明,如果不是则需要从祖宗画起,这样提交请求是脏A组件,但经过上面合并后可能并入已提交的脏B组件,而在这里可能要转而绘制B组件的祖宗C组件。
parent = c.getParent();
if(parent != null) {
x += c.getX();
y += c.getY();//处理坐标的迁移,使之相对父组件
c = parent;
} else {
break;
}
if(!(c instanceof JComponent)) {
break;
}
}
if(c instanceof JComponent) {
((JComponent)c) _paintImmediately (x,y,w,h);//向上追到一个opaque的轻量级父组件具体绘制(可找到该绘制的目标了)
} else {
c.repaint(x,y,w,h);//没有的话那就是顶层容器的绘制了。
}
}
void __paintImmediately(int x, int y, int w, int h) {
Graphics g;
Container c;
Rectangle b;
int tmpX, tmpY, tmpWidth, tmpHeight;
int offsetX=0,offsetY=0;
boolean hasBuffer = false;
JComponent bufferedComponent = null;
JComponent paintingComponent = this;
RepaintManager repaintManager = RepaintManager.currentManager(this);
// parent Container's up to Window or Applet. First container is
// the direct parent. Note that in testing it was faster to
// alloc a new Vector vs keeping a stack of them around, and gc
// seemed to have a minimal effect on this.
java.util.List<Component> path = new java.util.ArrayList<Component>(7);
int pIndex = -1;
int pCount = 0;
tmpX = tmpY = tmpWidth = tmpHeight = 0;//此后始终tmpX = tmpY==0
Rectangle paintImmediatelyClip = fetchRectangle();//从回收池里获取一个Rectangle
paintImmediatelyClip.x = x;
paintImmediatelyClip.y = y;
paintImmediatelyClip.width = w;
paintImmediatelyClip.height = h;//用来记录要绘制的区域
// System.out.println("1) ************* in _paintImmediately for " + this);
boolean ontop = alwaysOnTop() && isOpaque();//ontop说明了该组件一直在界面最上层不会被非子组件遮挡,如果==true,则下面那个大循环就做一件事情,找到该组件的顶层容器,此后就是从顶层容器拿到Graph2D开始绘制该组件。如果==false,则要考虑该组件有可能被非子组件(兄弟组件)遮挡的情况。
if (ontop) {
SwingUtilities.computeIntersection(0, 0, getWidth(), getHeight(),//我靠,这里又确定一次脏区是否超出组件当前范围
paintImmediatelyClip);
if (paintImmediatelyClip.width == 0) {//经交集判断后宽度为0(或高度为0)则意味着超出范围,回收Rectangle并返回。
recycleRectangle(paintImmediatelyClip);
return;
}
}
Component child;
for (c = this, child = null;
c != null && !(c instanceof Window) && !(c instanceof Applet);
child = c, c = c.getParent()) {
//ontop记录的始终是要绘制的组件的属性;循环将一直向上递推到顶层容器,因为要通过顶层容器拿到Graph2D;同时在这此循环中将要考虑兄弟组件相互遮挡的情况,可能因此重新设置要最终要绘制的组件,也就说这次循环实际遍历做了2件事情。
JComponent jc = (c instanceof JComponent) ? (JComponent)c :
null;//jc始终记录向上的轻量级父组件,如果某一级出现非轻量级父组件则为空,c则始终记录向上的组件,不管轻重。
path.add(c);//path记录向上递推的整个路径
if(!ontop && jc != null && !jc.isOptimizedDrawingEnabled()) {//如果不能确定那个发起绘制的组件一定是ontop,而且如果当前该组件是轻量级组件而且不能保证子组件不重叠,需要考虑那个要求绘制的子组件是否与兄弟组件有遮挡关系,从而确定不同的绘制路径
boolean resetPC;
// Children of c may overlap, three possible cases for the
// painting region:
// . Completely obscured by an opaque sibling, in which
// case there is no need to paint.
// . Partially obscured by a sibling: need to start
// painting from c.
// . Otherwise we aren't obscured and thus don't need to
// start painting from parent.
if (c != this) {//当前组件已经向上循环成要绘制的组件的老一辈,就是说当前已经循环过第一轮了。
if (jc.isPaintingOrigin()) {//如果该组件已经声明所有子组件的绘制请求都应该从本组件绘制起
resetPC = true;//则直接要求重新设置准备绘制的组件为当前组件
}
else {//如果该组件没有要求一定代理子组件的绘制,需要判断要求绘制的那个子组件是否有兄弟组件遮盖
Component[] children = c.getComponents();
int i = 0;
for (; i<children.length; i++) {
if (children[i] == child) break;//得到子组件的索引
}
switch (jc.getObscuredState(i,
paintImmediatelyClip.x,
paintImmediatelyClip.y,
paintImmediatelyClip.width,
paintImmediatelyClip.height)) {//检查前面加入的子组件是否将该子组件遮挡。这个地方绕了我一下!因为我有个错误认识,首先给容器加入子组件时childIndex是从0加起这是没错的,但我感觉是后面加入的总会覆盖前面的,所以这里应该判断后面加入的组件是否将该组件遮挡啊。但是实际上,容器绘制子组件时是从后面向前一直画到0,一测就明白了,是前面加入的后画,因此前面加入的子组件会覆盖后面加入的子组件。Awt为什么要这样倒序绘制?
case NOT_OBSCURED:
resetPC = false;//各不相干则不用考虑
break;
case COMPLETELY_OBSCURED://经判断那个子组件已经被某个前面加入的组件完全遮挡直接返回并忽略绘制请求,因为根据倒序绘制的顺序,现在屏幕上显示的是那个兄弟组件。
recycleRectangle(paintImmediatelyClip);
return;
default:
resetPC = true;//部分包含则要调整绘制组件,从父组件-当前组件画起,因为这样才能把那些涉嫌脏区的兄弟组件也给绘制出来。
break;
}
}
}
else {
resetPC = false;//仍然是当前组件,就是说第一轮循环,不需要下面reset绘制组件
}
if (resetPC) {
// Get rid of any buffer since we draw from here and
// we might draw something larger
paintingComponent = jc;//要绘制的组件转为当前组件
pIndex = pCount;//要绘制的组件相对目标组件的层次
offsetX = offsetY = 0;//通过清零是脏区在最终绘制前变成该当前组件的坐标系。
hasBuffer = false;//如果已经调整了绘制组件,则要重新初始化hasBuffer,因为此时该组件可能不支持双缓冲,而在此之前的循环中存在过bufferComponent父组件导致hasBuffer=true。因为要最终绘制组件时需要得到的参数bufferComponent应该是要绘制组件的父组件,而这里重新设置了要最终绘制的组件但还没对父组件进行遍历,所以需要重新初始化。
}
}
pCount++;//向上循环层数
// look to see if the parent (and therefor this component)
// is double buffered
//记录在向上的路径下最后一个支持双缓冲的父组件,而且只要最终要绘制的组件有支持双缓冲的祖宗组件就会使用双缓冲(hasBuffer==true)
if(repaintManager.isDoubleBufferingEnabled() && jc != null &&
jc.isDoubleBuffered()) {//如果当前组件获得双缓冲支持则需要置双缓冲标志
hasBuffer = true;
bufferedComponent = jc;
}
// if we aren't on top, include the parent's clip
if (!ontop) {//如果不能确定那个发起绘制的组件一定是ontop,需要重新调整脏区的坐标系
int bx = c.getX();
int by = c.getY();
tmpWidth = c.getWidth();
tmpHeight = c.getHeight();
SwingUtilities.computeIntersection(tmpX,tmpY,tmpWidth,tmpHeight,paintImmediatelyClip);
paintImmediatelyClip.x += bx;//使区域相对于当前组件的父组件
paintImmediatelyClip.y += by;
offsetX += bx;
offsetY += by;
}
}
//paintingComponent现在是要经上面循环调整过的要开始绘制的最终的那个组件,
// If the clip width or height is negative, don't bother painting
//此时再次校验是否有效绘制区域,否则忽略并回收区域,真谨慎,真不容易啊
if(c == null || c.getPeer() == null ||
paintImmediatelyClip.width <= 0 ||
paintImmediatelyClip.height <= 0) {
recycleRectangle(paintImmediatelyClip);
return;
}
paintingComponent.setFlag(IS_REPAINTING, true);
//调整paintImmediatelyClip,使其成为相对绘制组件的坐标系
paintImmediatelyClip.x -= offsetX;
paintImmediatelyClip.y -= offsetY;
// Notify the Components that are going to be painted of the
// child component to paint to.
if(paintingComponent != this) {//如果调整过最终绘制的组件,记录该组件的孩子路径,以后有用地
Component comp;
int i = pIndex;
for(; i > 0 ; i--) {
comp = path.get(i);
if(comp instanceof JComponent) {
((JComponent)comp).setPaintingChild(path.get(i-1));
}
}
}
//真正进行绘制
try {
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();
}
}
finally {
// Reset the painting child for the parent components.
if(paintingComponent != this) {//清空记录
Component comp;
int i = pIndex;
for(; i > 0 ; i--) {
comp = path.get(i);
if(comp instanceof JComponent) {
((JComponent)comp).setPaintingChild(null);
}
}
}//恢复标志
paintingComponent.setFlag(IS_REPAINTING, false);
}
recycleRectangle(paintImmediatelyClip);//回收Rectangle
}
上述的这段代码实际上最主要的精神在于,如果要绘制一个组件的某区域,swing需要知道该组件的这块区域是否被兄弟组件们给遮挡。有几个参数可以帮助swing快速判断,没有这几个优化参数的提醒,swing将通过向上遍历到顶层容器的大循环来确定所有可能的遮挡情况(可能该组件没有被兄弟组件遮挡,但其爷爷组件可能被二爷爷给遮挡,这也得要考虑)。注意,如果是多个顶层容器相互遮挡,那这个遍历是没有考虑的,所以可能因此导致没有必要的重画遮挡区。但是因为Graphic2D是从各自顶层容器获取,所以也不会出现穿透的效果。这个遍历无疑是耗费效率的,虽然它已经通过约定容器画子组件的顺序而尽量去减少遍历的节点,也通过多次检查可能的区域不合理来过滤,但是为提高性能还是应该注意如果要自行开发swing组件,要设置优化参数,同时GUI不应该无谓地增加组件层次。
发表评论
-
JAVA Painting-Swing实现纪要四
2008-12-17 09:01 1795JAVA Painting-Swing实现纪要四 前三节大概描 ... -
JAVA Painting-Swing实现纪要三
2008-12-14 17:57 2256JAVA Painting-Swing实现纪要三 前两节实现了 ... -
JAVA Painting-Swing实现纪要一
2008-11-28 15:38 5074首先推荐<Painting in AWT ... -
JAVA InputMethod 输入法实现纪要
2008-11-28 09:46 12635Jre1.7对输入法的支持使得java开发者能够方便地使用JA ... -
JAVA MouseEvent实现纪要
2008-11-28 09:45 1976Jre1.7鼠标事件以MouseEvent类封装。共有如下8种 ... -
Java Focus实现纪要三
2008-11-28 09:44 1155特别注意:按java给出的 ... -
Java Focus实现纪要二
2008-11-28 09:44 16061. 在Jre1.7版本 ... -
Java Focus实现纪要一
2008-11-28 09:43 2716窗口系统一般包含一个 ... -
Java dnd拖拽实现分析纪要
2008-11-28 09:41 2902Java dnd拖拽实现分析纪要 既有的Swing组件都内置 ...
相关推荐
从给出的文件名 "Painting-With-Music-源码.rar" 和 "Painting-With-Music-源码.zip" 来看,这很可能是该项目的源代码文件,包含了实现这一功能的所有编程代码。 在这个项目中,我们可以深入探讨以下几个关键知识点...
在这个名为“涂鸦wx-app-painting-master.zip”的压缩包中,我们可能找到了一个专门用于在微信小程序上实现涂鸦功能的项目。下面将详细探讨微信小程序的基础知识、涂鸦功能的实现以及可能包含的文件结构。 首先,...
This is a painting-tool. You can choose several drawing options from a toolbar and you can apply much filters and effects to the pic. Pls. give feedback!
绘画是一种创造性和想象力的艺术形式,许多人通过绘画表达自己的情感和思想。但传统绘画需要大量时间和精力,对许多人来说是困难的。...用户可以轻松进行各种绘画操作,实现自己的创意和想象,帮助广大绘画爱好者。
2. **详尽的项目管理**:Painting-Pro-Estimator允许您跟踪每个项目的进度,从初步评估到最终结算。这包括对工作量的详细分解,如墙面面积、颜色选择、涂料类型等,确保无遗漏。 3. **准确的成本计算**:软件内置了...
通过监听音乐的音频数据,项目将音乐的节奏、频率等元素转化为视觉效果,实现了音乐与绘画的实时互动。 在"Painting-With-Music-master"这个压缩包中,包含了项目的所有源代码和资源文件。开发者可能通过分析这些...
中国传统山水画数据集 文章标题:“利用生成的对抗网络进行端到端的中国山水画创作” ArXiv: : 抽象的:当前基于GAN的艺术生成方法...数据集包含2,192幅高质量的中国传统山水画。来自以下来源的所有绘画尺寸均为512x5
总的来说,"wash-painting-ui"项目是将传统艺术与现代技术完美融合的典范,通过Vue3和Vite的技术支持,实现了富有创意的水墨风格UI设计。每个组件都精心设计,力求在用户体验与艺术美感之间找到最佳平衡。这种大胆的...
在这个"painting-project"中,`<canvas>`元素是关键,它提供了一个画布,JavaScript通常会与之配合,通过Canvas API来实现动态绘图。Canvas API允许开发者在网页上进行像素级别的操作,绘制线条、形状、图像,甚至...
2. **绘图函数**:实现画线、画圆、画曲线等功能的函数,它们通常基于数学公式和优化算法。 3. **颜色处理**:可能包含对RGB值的操作,混合颜色,以及应用透明度(阿尔法通道)的算法。 4. **图像处理库**:如PIL...
"light-painting-wand"项目是一个创新的科技艺术工具,专为光绘摄影爱好者和艺术家设计。光绘,又称光涂鸦,是一种摄影技术,通过在黑暗中移动光源来创造艺术图像。这个项目旨在提供一个名为“轻涂棒”的硬件设备,...
总之,"java 画图软件_java_" 提供了一个用Java实现的简单图形绘制工具的例子,展示了如何利用Java的GUI库来创建具有图形绘制能力的桌面应用程序。通过深入学习和理解这个项目,开发者可以进一步提升他们的Java GUI...
"dot-painting-challenge"项目就是一个很好的例子,它利用编程语言的力量,借助乌龟图形库和颜色图库,以彩色圆点的形式创作出各种图像。本文将深入探讨这一挑战背后的技术细节。 首先,乌龟图形库(Turtle ...
从标题来看,它很可能涉及到计算机图形学中的3D建模和绘制技术,特别是在C#环境中实现的。下面我们将深入探讨与这个项目相关的C#编程和3D图形处理的知识点。 首先,C#是一种多范式编程语言,由微软开发,广泛用于...
5. **绘图(painting)**:在"painting"目录中,你将看到如何利用Java的Graphics2D API进行图形绘制。这包括在组件上绘制线条、形状、图像甚至动画,提供了自定义组件外观的强大能力。 通过深入学习这些示例,你...
合并单元格绘画-基于图像的分析管道配方 :...目前,这两种数据成分的测量都是由CellProfiler软件进行的(使用定制的Pooled Cell Painting插件)。 配方步骤 所有食谱还包括针对每个食谱的特定说明或步骤。 我们的食谱
2. **库和框架**:可能会包含像jQuery或React这样的JavaScript库或框架,用于简化DOM操作,提升性能,或者提供更高级的功能如状态管理。 3. **画布元素**:应用的核心部分可能是基于HTML5 `<canvas>` 元素构建的。...
2. 作品集:展示NJB Painting Inc的绘画作品,可能通过滑动或网格布局呈现。 3. 关于我们:介绍公司历史、团队成员和价值观。 4. 联系我们:包含联系表单或联系方式,方便潜在客户进行咨询。 5. 博客或新闻:如果...
2-安装程序的详细说明:motor et Encoder 倒入Arduino和copier les deux dossiers motor et Encoder的全部文件文档/ Arduino / librairies 3-Ouvrir le fichiersouhaitéavec Arduino IDE 特殊形式的证明书,...
在本项目中,"MFC-painting-(Kaleidoscope).rar"是一个使用Microsoft Foundation Class (MFC)库开发的Visual C++应用,主要目的是实现一个动态的万花筒效果。MFC是微软提供的一套面向对象的C++类库,它封装了Windows...