/*
* Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.swing;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.VolatileImage;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.applet.*;
import jdk.internal.access.JavaSecurityAccess;
import jdk.internal.access.SharedSecrets;
import sun.awt.AWTAccessor;
import sun.awt.AppContext;
import sun.awt.DisplayChangedListener;
import sun.awt.SunToolkit;
import sun.java2d.SunGraphicsEnvironment;
import sun.security.action.GetPropertyAction;
import com.sun.java.swing.SwingUtilities3;
import java.awt.geom.AffineTransform;
import sun.java2d.SunGraphics2D;
import sun.java2d.pipe.Region;
import sun.swing.SwingAccessor;
import sun.swing.SwingUtilities2;
import sun.swing.SwingUtilities2.RepaintListener;
/**
* This class manages repaint requests, allowing the number
* of repaints to be minimized, for example by collapsing multiple
* requests into a single repaint for members of a component tree.
* <p>
* As of 1.6 <code>RepaintManager</code> handles repaint requests
* for Swing's top level components (<code>JApplet</code>,
* <code>JWindow</code>, <code>JFrame</code> and <code>JDialog</code>).
* Any calls to <code>repaint</code> on one of these will call into the
* appropriate <code>addDirtyRegion</code> method.
*
* @author Arnaud Weber
* @since 1.2
*/
public class RepaintManager
{
/**
* Whether or not the RepaintManager should handle paint requests
* for top levels.
*/
static final boolean HANDLE_TOP_LEVEL_PAINT;
private static final short BUFFER_STRATEGY_NOT_SPECIFIED = 0;
private static final short BUFFER_STRATEGY_SPECIFIED_ON = 1;
private static final short BUFFER_STRATEGY_SPECIFIED_OFF = 2;
private static final short BUFFER_STRATEGY_TYPE;
/**
* Maps from GraphicsConfiguration to VolatileImage.
*/
private Map<GraphicsConfiguration,VolatileImage> volatileMap = new
HashMap<GraphicsConfiguration,VolatileImage>(1);
//
// As of 1.6 Swing handles scheduling of paint events from native code.
// That is, SwingPaintEventDispatcher is invoked on the toolkit thread,
// which in turn invokes nativeAddDirtyRegion. Because this is invoked
// from the native thread we can not invoke any public methods and so
// we introduce these added maps. So, any time nativeAddDirtyRegion is
// invoked the region is added to hwDirtyComponents and a work request
// is scheduled. When the work request is processed all entries in
// this map are pushed to the real map (dirtyComponents) and then
// painted with the rest of the components.
//
private Map<Container,Rectangle> hwDirtyComponents;
private Map<Component,Rectangle> dirtyComponents;
private Map<Component,Rectangle> tmpDirtyComponents;
private java.util.List<Component> invalidComponents;
// List of Runnables that need to be processed before painting from AWT.
private java.util.List<Runnable> runnableList;
boolean doubleBufferingEnabled = true;
private Dimension doubleBufferMaxSize;
private boolean isCustomMaxBufferSizeSet = false;
// Support for both the standard and volatile offscreen buffers exists to
// provide backwards compatibility for the [rare] programs which may be
// calling getOffScreenBuffer() and not expecting to get a VolatileImage.
// Swing internally is migrating to use *only* the volatile image buffer.
// Support for standard offscreen buffer
//
DoubleBufferInfo standardDoubleBuffer;
/**
* Object responsible for hanlding core paint functionality.
*/
private PaintManager paintManager;
private static final Object repaintManagerKey = RepaintManager.class;
// Whether or not a VolatileImage should be used for double-buffered painting
static boolean volatileImageBufferEnabled = true;
/**
* Type of VolatileImage which should be used for double-buffered
* painting.
*/
private static final int volatileBufferType;
/**
* Value of the system property awt.nativeDoubleBuffering.
*/
private static boolean nativeDoubleBuffering;
// The maximum number of times Swing will attempt to use the VolatileImage
// buffer during a paint operation.
private static final int VOLATILE_LOOP_MAX = 2;
/**
* Number of <code>beginPaint</code> that have been invoked.
*/
private int paintDepth = 0;
/**
* Type of buffer strategy to use. Will be one of the BUFFER_STRATEGY_
* constants.
*/
private short bufferStrategyType;
//
// BufferStrategyPaintManager has the unique characteristic that it
// must deal with the buffer being lost while painting to it. For
// example, if we paint a component and show it and the buffer has
// become lost we must repaint the whole window. To deal with that
// the PaintManager calls into repaintRoot, and if we're still in
// the process of painting the repaintRoot field is set to the JRootPane
// and after the current JComponent.paintImmediately call finishes
// paintImmediately will be invoked on the repaintRoot. In this
// way we don't try to show garbage to the screen.
//
/**
* True if we're in the process of painting the dirty regions. This is
* set to true in <code>paintDirtyRegions</code>.
*/
private boolean painting;
/**
* If the PaintManager calls into repaintRoot during painting this field
* will be set to the root.
*/
private JComponent repaintRoot;
/**
* The Thread that has initiated painting. If null it
* indicates painting is not currently in progress.
*/
private Thread paintThread;
/**
* Runnable used to process all repaint/revalidate requests.
*/
private final ProcessingRunnable processingRunnable;
private static final JavaSecurityAccess javaSecurityAccess =
SharedSecrets.getJavaSecurityAccess();
/**
* Listener installed to detect display changes. When display changes,
* schedules a callback to notify all RepaintManagers of the display
* changes.
*/
private static final DisplayChangedListener displayChangedHandler =
new DisplayChangedHandler();
static {
SwingAccessor.setRepaintManagerAccessor(new SwingAccessor.RepaintManagerAccessor() {
@Override
public void addRepaintListener(RepaintManager rm, RepaintListener l) {
rm.addRepaintListener(l);
}
@Override
public void removeRepaintListener(RepaintManager rm, RepaintListener l) {
rm.removeRepaintListener(l);
}
});
volatileImageBufferEnabled = "true".equals(AccessController.
doPrivileged(new GetPropertyAction(
"swing.volatileImageBufferEnabled", "true")));
boolean headless = GraphicsEnvironment.isHeadless();
if (volatileImageBufferEnabled && headless) {
volatileImageBufferEnabled = false;
}
nativeDoubleBuffering = "true".equals(AccessController.doPrivileged(
new GetPropertyAction("awt.nativeDoubleBuffering")));
String bs = AccessController.doPrivileged(
new GetPropertyAction("swing.bufferPerWindow"));
if (headless) {
BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_OFF;
}
else if (bs == null) {
BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_NOT_SPECIFIED;
}
else if ("true".equals(bs)) {
BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_ON;
}
else {
BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_OFF;
}
HANDLE_TOP_LEVEL_PAINT = "true".equals(AccessController.doPrivileged(
new GetPropertyAction("swing.handleTopLevelPaint", "true")));
GraphicsEnvironment ge = GraphicsEnvironment.
getLocalGraphicsEnvironment();
if (ge instanceof SunGraphicsEnvironment) {
((SunGraphicsEnvironment) ge).addDisplayChangedListener(
displayChangedHandler);
}
Toolkit tk = Toolkit.getDefaultToolkit();
if ((tk instanceof SunToolkit)
&& ((SunToolkit) tk).isSwingBackbufferTranslucencySupported()) {
volatileBufferType = Transparency.TRANSLUCENT;
} else {
volatileBufferType = Transparency.OPAQUE;
}
}
/**
* Return the RepaintManager for the calling thread given a Component.
*
* @param c a Component -- unused in the default implementation, but could
* be used by an overridden version to return a different RepaintManager
* depending on the Component
* @return the RepaintManager object
*/
public static RepaintManager currentManager(Component c) {
// Note: DisplayChangedRunnable passes in null as the component, so if
// component is ever used to determine the current
// RepaintManager, DisplayChangedRunnable will need to be modified
// accordingly.
return currentManager(AppContext.getAppContext());
}
/**
* Returns the RepaintManager for the specified AppContext. If
* a RepaintManager has not been created for the specified
* AppContext this will return null.
*/
static RepaintManager currentManager(AppContext appContext) {
RepaintManager rm = (RepaintManager)appContext.get(repaintManagerKey);
if (rm == null) {
rm = new RepaintManager(BUFFER_STRATEGY_TYPE);
appContext.put(repaintManagerKey, rm);
}
return rm;
}
/**
* Return the RepaintManager for the calling thread given a JComponent.
* <p>
* Note: This method exists for backward binary compatibility with earlier
* versions of the Swing library. It simply returns the result returned by
* {@link #currentManager(Component)}.
*
* @param c a JComponent -- unused
* @return the RepaintManager object
*/
public static RepaintManager currentManager(JComponent c) {
return currentManager((Component)c);
}
/**
* Set the RepaintManager that should be used for the calling
* thread. <b>aRepaintManager</b> will become the current RepaintManager
* for the calling thread's thread group.
* @param aRepaintManager the RepaintManager object to use
*/
public static void setCurrentManager(RepaintManager aRepaintManager) {
if (aRepaintManager != null) {
SwingUtilities.appContextPut(repaintManagerKey, aRepaintManager);
} else {
SwingUtilities.appContextRemove(repaintManagerKey);
}
}
/**
* Create a new RepaintManager instance. You rarely call this constructor.
* directly. To get the default RepaintManager, use
* RepaintManager.currentManager(JComponent) (normally "this").
*/
public RepaintManager() {
// Because we can't know what a subclass is doing with the
// volatile image we immediately punt in subclasses. If this
// poses a problem we'll need a more sophisticated detection algorithm,
// or API.
this(BUFFER_STRATEGY_SPECIFIED_OFF);
}
private RepaintManager(short bufferStrategyType) {
// If native doublebuffering is being used, do NOT use
// Swing doublebuffering.
doubleBufferingEnabled = !nativeDoubleBuffering;
synchronized(this) {
dirtyComponents = new IdentityHashMap<Component,Rectangle>();
tmpDirtyComponents = new IdentityHashMap<Component,Rectangle>();
this.bufferStrategyType = bufferStrategyType;
hwDirtyComponents = new IdentityHashMap<Container,Rectangle>();
}
processingRunnable = new ProcessingRunnable();
}
private void displayChanged() {
if (isCustomMaxBufferSizeSet) {
clearImages();
} else {
// Reset buffer maximum size to get valid size from updated graphics
// environment in getDoubleBufferMaximumSize()
setDoubleBufferMaximumSize(null);
}
}
/**
* Mark the component as in need of layout and queue a runnable
* for the event dispatching thread that will validate the components
* first isValidateRoot() ancestor.
*
* @param invalidComponent a component
* @see JComponent#isValidateRoot
* @see #removeInvalidComponent
*/
public synchronized void addInvalidComponent(JComponent invalidComponent)
{
RepaintManager delegate = getDelegate(invalidComponent);
if (delegate != null) {
delegate.addInvalidComponent(invalidComponent);
return;
}
Component validateRoot =
SwingUtilities.getValidateRoot(invalidComponent, true);
if (validateRoot == null) {
return;
}
/* Lazily create the invalidateComponents vector and add the
* validateRoot if it's not there already. If this validateRoot
* is already in the vector, we're done.
*/
if (invalidComponents == null) {
invalidComponents = new ArrayList<Component>();
}
else {
int n = invalidComponents.size();
for(int i = 0; i < n; i++) {
if(validateRoot == invalidComponents.get(i)) {
return;
}
}
}
invalidComponents.add(validateRoot);
// Queue a Runnable to invoke paintDirtyRegions and
// validateInvalidComponents.
scheduleProcessingRunnable(SunToolkit.targetToAppContext(invalidComponent));
}
/**
* Remove a component from the list of invalid components.
*
* @param component a component
* @see #addInvalidComponent
*/
public synchronized void removeInvalidComponent(JComponent component) {
RepaintManager delegate = getDelegate(component);
if (delegate != null) {
delegate.removeInvalidComponent(component);
return;
}
if(invalidComponents != null) {
int index = invalidComponents.indexOf(component);
if(index != -1) {
invalidComponents.remove(index);
}
}
}
/**
* 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
*/
@SuppressWarnings("deprecation")
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.isDisplayable()) {
return;
}
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;
synchronized(this) {
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(SunToolkit.targetToAppContext(c));
}
/**
* 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.
*
* @param c Component to repaint, null results in nothing happening.
* @param x X coordinate of the region to repaint
* @param y Y coordinate of the region to repaint
* @param w Width of the region to repaint
* @param h Height of the region to repaint
* @see JComponent#repaint
*/
public void addDirtyRegion(JComponent c, int x, int y, int w, int h)
{
RepaintManager delegate = getDelegate(c);
if (delegate != null) {
delegate.addDirtyRegion(c, x, y, w, h);
return;
}
addDirtyRegion0(c, x, y, w, h);
}
/**
* Adds <code>window</code> to the list of <code>Component</code>s that
* need to be repainted.
*
* @param window Window to repaint, null results in nothing happening.
* @param x X coordinate of the region to repaint
* @param y Y coordinate of the region to repaint
* @param w Width of the region to repaint
* @param h Height of the region to repaint
* @see JFrame#repaint
* @see JWindow#repaint
* @see JDialog#repaint
* @since 1.6
*/
public void addDirtyRegion(Window window, int x, int y, int w, int h) {
addDirtyRegion0(window, x, y, w, h);
}
/**
* Adds <code>applet</code> to the list of <code>Component</code>s that
* need to be repainted.
*
* @param applet Applet to repaint, null results in nothing happening.
* @param x X coordinate of the region to repaint
* @param y Y coordinate of the region to repaint
* @param w Width of the region to repaint
* @param h Height of the region to repaint
* @see JApplet#repaint
* @since 1.6
*
* @deprecated The Applet API is deprecated. See the
* <a href="../../java/applet/package-summary.html"> java.applet package
* documentation</a> for further information.
*/
@Deprecated(since = "9")
public void addDirtyRegion(Applet applet, int x, int y, int w, int h) {
addDirtyRegion0(applet, x, y, w, h);
}
@SuppressWarnings("deprecation")
void scheduleHeavyWeightPaints() {
Map<Container,Rectangle> hws;
synchronized(this) {
if (hwDirtyComponents.size() == 0) {
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);
}
}
}
//
// This is called from the toolkit thread when a native expose is
// received.
//
void nativeAddDirtyRegion(AppContext appContext, Container c,
int x, int y, int w, int h) {
if (w > 0 && h > 0) {
synchronized(this) {
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 called from the toolkit thread when awt needs to run a
// Runnable before we paint.
//
void nativeQueueSurfaceDataRunnable(AppContext appContext,
final Component c, final Runnable r)
{
synchronized(this) {
if (runnableList == null) {
runnableList = new LinkedList<Runnable>();
}
runnableList.add(new Runnable() {
public void run() {
AccessControlContext stack = AccessController.getContext();
AccessControlContext acc =
AWTAccessor.getComponentAccessor().getAccessControlContext(c);
javaSecurityAccess.doIntersectionPrivilege(new PrivilegedAction<Void>() {
public Void run() {
r.run();
return null;
}
}, stack, acc);
}
});
}
scheduleProcessingRunnable(appContext);
}
/**
* 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 = 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;
}
/**
* Return the current dirty region for a component.
* Return an empty rectangle if the component is not
* dirty.
*
* @param aComponent a component
* @return the region
*/
public Rectangle getDirtyRegion(JComponent aComponent) {
RepaintManager delegate = getDelegate(aComponent);
if (delegate != null) {
return delegate.getDirtyRegion(aComponent);
}
Rectangle r;
synchronized(this) {
r = dirtyComponents.get(aComponent);
}
if(r == null)
return new Rectangle(0,0,0,0);
else
return new Rectangle(r);
}
/**
* Mark a component completely dirty. <b>aComponent</b> will be
* completely painted during the next paintDirtyRegions() call.
*
* @param aComponent a component
*/
public void markCompletelyDirty(JComponent aComponent) {
RepaintManager delegate = getDelegate(aComponent);
if (delegate != null) {
delegate.markCompletelyDirty(aComponent);
return;
}
addDirtyRegion(aComponent,0,0,Integer.MAX_VALUE,Integer.MAX_VALUE);
}
/**
* Mark a component completely clean. <b>aComponent</b> will not
* get painted during the next paintDirtyRegions() call.
*
* @param aComponent a component
*/
public void markCompletelyClean(JComponent aComponent) {
RepaintManager delegate = getDelegate(aComponent);
if (delegate != null) {
delegate.markCompletelyClean(aComponent);
return;
}
synchronized(this) {
dirtyComponents.remove(aComponent);
}
}
/**
* Convenience method that returns true if <b>aComponent</b> will be completely
* painted during the next paintDirtyRegions(). If computing dirty regions is
* expensive for your component, use this method and avoid computing dirty region
* if it return true.
*
* @param aComponent a component
* @return {@code true} if <b>aComponent</b> will be completely
* painted during the next paintDirtyRegions().
*/
public boolean isCompletelyDirty(JComponent aComponent) {
RepaintManager delegate = getDelegate(aComponent);
if (delegate != null) {
return delegate.isCompletelyDirty(aComponent);
}
Rectangle r;
r = getDirtyRegion(aComponent);
if(r.width == Integer.MAX_VALUE &&
r.height == Integer.MAX_VALUE)
return true;
else
return false;
}
/**
* Validate all of the components that have been marked invalid.
* @see #addInvalidComponent
*/
public void validateInvalidComponents() {
final java.util.List<Component> ic;
synchronized(this) {
if (invalidComponents == null) {
return;
}
ic = invalidComponents;
invalidComponents = null;
}
int n = ic.size();
for(int i = 0; i < n; i++) {
final Component c = ic.get(i);
AccessControlContext stack = AccessController.getContext();
AccessControlContext acc =
AWTAccessor.getComponentAccessor().getAccessControlContext(c);
javaSecurityAccess.doIntersectionPrivilege(
new PrivilegedAction<Void>() {
public Void run() {
c.validate();
return null;
}
}, stack, acc);
}
}
/**
* This is invoked to process paint requests. It's needed
* for backward compatibility 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) {
runnable.run();
}
}
paintDirtyRegions();
if (dirtyComponents.size() > 0) {
// This'll only happen if a subclass isn't correctly dealing
// with toplevels.
paintDirtyRegions(dirtyComponents);
}
}
private void updateWindows(Map<Component,Rectangle> dirtyComponents) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
if (!(toolkit instanceof SunToolkit &&
((SunToolkit)toolkit).needUpdateWindow()))
{
return;
}
Set<Window> windows = new HashSet<Window>();
Set<Component> dirtyComps = dirtyComponents.keySet();
for (Iterator<Component> it = dirtyComps.iterator(); it.hasNext();) {
Component dirty = it.next();
Window window = dirty instanceof Window ?
(Window)dirty :
SwingUtilities.getWindowAncestor(dirty);
if (window != null &&
!window.isOpaque())
{
windows.add(window);
}
}
for (Window window : windows) {
AWTAccessor.getWindowAccessor().updateWindow(window);
}
}
boolean isPainting() {
return painting;
}
/**
* 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();
}
paintDirtyRegions(tmpDirtyComponents);
}
private void paintDirtyRegions(
final Map<Component,Rectangle> tmpDirtyComponents)
{
if (tmpDirtyComponents.isEmpty()) {
return;
}
final java.util.List<Component> roots =
new ArrayList<Component>(tmpDirtyComponents.size());
for (Component dirty : tmpDirtyComponents.keySet()) {
collectDirtyComponents(tmpDirtyComponents, dirty, roots);
}
final AtomicInteger count = new AtomicInteger(roots.size());
painting = true;
try {
for (int j=0 ; j < count.get(); j++) {
final int i = j;
final Component dirtyComponent = roots.get(j);
AccessControlContext stack = AccessController.getContext();
AccessControlContext acc =
AWTAccessor.getComponentAccessor().getAccessControlContext(dirtyComponent);
javaSecurityAccess.doIntersectionPrivilege(new PrivilegedAction<Void>() {
public Void run() {
Rectangle rect = tmpDirtyComponents.get(dirtyComponent);
// Sometimes when RepaintManager is changed during the painting
// we may get null here, see #6995769 for details
if (rect == null) {
return null;
}
int localBoundsH = dirtyComponent.getHeight();
int localBoundsW = dirtyComponent.getWidth();
SwingUtilities.computeIntersection(0,
0,
localBoundsW,
localBoundsH,
rect);
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);
} 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.set(roots.size());
paintManager.isRepaintingRoot = true;
repaintRoot.paintImmediately(0, 0, repaintRoot.getWidth(),
repaintRoot.getHeight());
paintManager.isRepaintingRoot = false;
// Only service repaintRoot once.
repaintRoot = null;
}
return null;
}
}, stack, acc);
}
} finally {
painting = false;
}
updateWindows(tmpDirtyComponents);
tmpDirtyComponents.clear();
}
/**
* Removes any components from roots that are children of
* root.
*/
private void adjustRoots(JComponent root,
java.util.List<Component> roots, int index) {
for (int i = roots.size() - 1; i >= index; i--) {
Component c = roots.get(i);
for(;;) {
if (c == root || c == null || !(c instanceof JComponent)) {
break;
}
c = c.getParent();
}
if (c == root) {
roots.remove(i);
}
}
}
Rectangle tmp = new Rectangle();
void collectDirtyComponents(Map<Component,Rectangle> dirtyComponents,
Component dirtyComponent,
java.util.List<Component> roots) {
int dx, dy, rootDx, rootDy;
Component component, rootDirtyComponent,parent;
Rectangle cBounds;
// Find the highest parent which is dirty. When we get out of this
// rootDx and rootDy will contain the translation from the
// rootDirtyComponent's coordinate system to the coordinates of the
// original dirty component. The tmp Rect is also used to compute the
// visible portion of the dirtyRect.
component = rootDirtyComponent = dirtyComponent;
int x = dirtyComponent.getX();
int y = dirtyComponent.getY();
int w = dirtyComponent.getWidth();
int h = dirtyComponent.getHeight();
dx = rootDx = 0;
dy = rootDy = 0;
tmp.setBounds(dirtyComponents.get(dirtyComponent));
// System.out.println("Collect dirty component for bound " + tmp +
// "component bounds is " + cBounds);;
SwingUtilities.computeIntersection(0,0,w,h,tmp);
if (tmp.isEmpty()) {
// System.out.println("Empty 1");
return;
}
for(;;) {
if(!(component instanceof JComponent))
break;
parent = component.getParent();
if(parent == null)
break;
component = parent;
dx += x;
dy += y;
tmp.setLocation(tmp.x + x, tmp.y + y);
x = component.getX();
y = component.getY();
w = component.getWidth();
h = component.getHeight();
tmp = SwingUtilities.computeIntersection(0,0,w,h,tmp);
if (tmp.isEmpty()) {
// System.out.println("Empty 2");
return;
}
if (dirtyComponents.get(component) != null) {
rootDirtyComponent = component;
rootDx = dx;
rootDy = dy;
}
}
if (dirtyComponent != rootDirtyComponent) {
Rectangle r;
tmp.setLocation(tmp.x + rootDx - dx,
tmp.y + rootDy - dy);
r = dirtyComponents.get(rootDirtyComponent);
SwingUtilities.computeUnion(tmp.x,tmp.y,tmp.width,tmp.height,r);
}
// If we haven't seen this root before, then we need to add it to the
// list of root dirty Views.
if (!roots.contains(rootDirtyComponent))
roots.add(rootDirtyComponent);
}
/**
* Returns a string that displays and identifies this
* object's properties.
*
* @return a String representation of this object
*/
public synchronized String toString() {
StringBuilder sb = new StringBuilder();
if(dirtyComponents != null)
sb.append("" + dirtyComponents);
return sb.toString();
}
/**
* Return the offscreen buffer that should be used as a double buffer with
* the component <code>c</code>.
* By default there is a double buffer per RepaintManager.
* The buffer might be smaller than <code>(proposedWidth,proposedHeight)</code>
* This happens when the maximum double buffer size as been set for the receiving
* repaint manager.
*
* @param c the component
* @param proposedWidth the width of the buffer
* @param proposedHeight the height of the buffer
*
* @return the image
*/
public Image getOffscreenBuffer(Component c,int proposedWidth,int proposedHeight) {
RepaintManager delegate = getDelegate(c);
if (delegate != null) {
return delegate.getOffscreenBuffer(c, proposedWidth, proposedHeight);
}
return _getOffscreenBuffer(c, proposedWidth, proposedHeight);
}
/**
* Return a volatile offscreen buffer that should be used as a
* double buffer with the specified component <code>c</code>.
* The image returned will be an instance of VolatileImage, or null
* if a VolatileImage object could not be instantiated.
* This buffer might be smaller than <code>(proposedWidth,proposedHeight)</code>.
* This happens when the maximum double buffer size has been set for this
* repaint manager.
*
* @param c the component
* @param proposedWidth the width of the buffer
* @param proposedHeight the height of the buffer
*
* @return the volatile image
* @see java.awt.image.VolatileImage
* @since 1.4
*/
public Image getVolatileOffscreenBuffer(Component c,
int proposedWidth,int proposedHeight) {
RepaintManager delegate = getDelegate(c);
if (delegate != null) {
return delegate.getVolatileOffscreenBuffer(c, proposedWidth,
proposedHeight);
}
// If the window is non-opaque, it's double-buffered at peer's level
Window w = (c instanceof Window) ? (Window)c : SwingUtilities.getWindowAncestor(c);
if (!w.isOpaque()) {
Toolkit tk = Toolkit.getDefaultToolkit();
if ((tk instanceof SunToolkit) && (((SunToolkit)tk).needUpdateWindow())) {
return null;
}
}
GraphicsConfiguration config = c.getGraphicsConfiguration();
if (config == null) {
config = GraphicsEnvironment.getLocalGraphicsEnvironment().
getDefaultScreenDevice().getDefaultConfiguration();
}
Dimension maxSize = getDoubleBufferMaximumSize();
int width = proposedWidth < 1 ? 1 :
(proposedWidth > maxSize.width? maxSize.width : proposedWidth);
int height = proposedHeight < 1 ? 1 :
(proposedHeight > maxSize.height? maxSize.height : proposedHeight);
VolatileImage image = volatileMap.get(config);
if (image == null || image.getWidth() < width ||
image.getHeight() < height) {
if (image != null) {
image.flush();
}
image = config.createCompatibleVolatileImage(width, height,
volatileBufferType);
volatileMap.put(config, image);
}
return image;
}
private Image _getOffscreenBuffer(Component c, int proposedWidth, int proposedHeight) {
Dimension maxSize = getDoubleBufferMaximumSize();
DoubleBufferInfo doubleBuffer;
int width, height;
// If the window is non-opaque, it's double-buffered at peer's level
Window w = (c instanceof Window) ? (Window)c : SwingUtilities.getWindowAncestor(c);
if (!w.isOpaque()) {
Toolkit tk = Toolkit.getDefaultToolkit();
if ((tk instanceof SunToolkit) && (((SunToolkit)tk).needUpdateWindow())) {
return null;
}
}
if (standardDoubleBuffer == null) {
standardDoubleBuffer = new DoubleBufferInfo();
}
doubleBuffer = standardDoubleBuffer;
width = proposedWidth < 1? 1 :
(proposedWidth > maxSize.width? maxSize.width : proposedWidth);
height = proposedHeight < 1? 1 :
(proposedHeight > maxSize.height? maxSize.height : proposedHeight);
if (doubleBuffer.needsReset || (doubleBuffer.image != null &&
(doubleBuffer.size.width < width ||
doubleBuffer.size.height < height))) {
doubleBuffer.needsReset = false;
if (doubleBuffer.image != null) {
doubleBuffer.image.flush();
doubleBuffer.image = null;
}
width = Math.max(doubleBuffer.size.width, width);
height = Math.max(doubleBuffer.size.height, height);
}
Image result = doubleBuffer.image;
if (doubleBuffer.image == null) {
result = c.createImage(width , height);
doubleBuffer.size = new Dimension(width, height);
if (c instanceof JComponent) {
((JComponent)c).setCreatedDoubleBuffer(true);
doubleBuffer.image = result;
}
// JComponent will inform us when it is no longer valid
// (via removeNotify) we have no such hook to other components,
// therefore we don't keep a ref to the Component
// (indirectly through the Image) by stashing the image.
}
return result;
}
/**
* Set the maximum double buffer size.
*
* @param d the dimension
*/
public void setDoubleBufferMaximumSize(Dimension d) {
doubleBufferMaxSize = d;
if (doubleBufferMaxSize == null) {
isCustomMaxBufferSizeSet = false;
clearImages();
} else {
isCustomMaxBufferSizeSet = true;
clearImages(d.width, d.height);
}
}
private void clearImages() {
clearImages(0, 0);
}
private void clearImages(int width, int height) {
if (standardDoubleBuffer != null && standardDoubleBuffer.image != null) {
if (standardDoubleBuffer.image.getWidth(null) > width ||
standardDoubleBuffer.image.getHeight(null) > height) {
standardDoubleBuffer.image.flush();
standardDoubleBuffer.image = null;
}
}
// Clear out the VolatileImages
Iterator<GraphicsConfiguration> gcs = volatileMap.keySet().iterator();
while (gcs.hasNext()) {
GraphicsConfiguration gc = gcs.next();
VolatileImage image = volatileMap.get(gc);
if (image.getWidth() > width || image.getHeight() > height) {
image.flush();
gcs.remove();
}
}
}
/**
* Returns the maximum double buffer size.
*
* @return a Dimension object representing the maximum size
*/
public Dimension getDoubleBufferMaximumSize() {
if (doubleBufferMaxSize == null) {
try {
Rectangle virtualBounds = new Rectangle();
GraphicsEnvironment ge = GraphicsEnvironment.
getLocalGraphicsEnvironment();
for (GraphicsDevice gd : ge.getScreenDevices()) {
GraphicsConfiguration gc = gd.getDefaultConfiguration();
virtualBounds = virtualBounds.union(gc.getBounds());
}
doubleBufferMaxSize = new Dimension(virtualBounds.width,
virtualBounds.height);
} catch (HeadlessException e) {
doubleBufferMaxSize = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
}
return doubleBufferMaxSize;
}
/**
* Enables or disables double buffering in this RepaintManager.
* CAUTION: The default value for this property is set for optimal
* paint performance on the given platform and it is not recommended
* that programs modify this property directly.
*
* @param aFlag true to activate double buffering
* @see #isDoubleBufferingEnabled
*/
public void setDoubleBufferingEnabled(boolean aFlag) {
doubleBufferingEnabled = aFlag;
PaintManager paintManager = getPaintManager();
if (!aFlag && paintManager.getClass() != PaintManager.class) {
setPaintManager(new PaintManager());
}
}
/**
* Returns true if this RepaintManager is double buffered.
* The default value for this property may vary from platform
* to platform. On platforms where native double buffering
* is supported in the AWT, the default value will be <code>false</code>
* to avoid unnecessary buffering in Swing.
* On platforms where native double buffering is not supported,
* the default value will be <code>true</code>.
*
* @return true if this object is double buffered
*/
public boolean isDoubleBufferingEnabled() {
return doubleBufferingEnabled;
}
/**
* This resets the double buffer. Actually, it marks the double buffer
* as invalid, the double buffer will then be recreated on the next
* invocation of getOffscreenBuffer.
*/
void resetDoubleBuffer() {
if (standardDoubleBuffer != null) {
standardDoubleBuffer.needsReset = true;
}
}
/**
* This resets the volatile double buffer.
*/
void resetVolatileDoubleBuffer(GraphicsConfiguration gc) {
Image image = volatileMap.remove(gc);
if (image != null) {
image.flush();
}
}
/**
* Returns true if we should use the <code>Image</code> returned
* from <code>getVolatileOffscreenBuffer</code> to do double buffering.
*/
boolean useVolatileDoubleBuffer() {
return volatileImageBufferEnabled;
}
/**
* Returns true if the current thread is the thread painting. This
* will return false if no threads are painting.
*/
private synchronized boolean isPaintingThread() {
return (Thread.currentThread() == paintThread);
}
//
// Paint methods. You very, VERY rarely need to invoke these.
// They are invoked directly from JComponent's painting code and
// when painting happens outside the normal flow: DefaultDesktopManager
// and JViewport. If you end up needing these methods in other places be
// careful that you don't get stuck in a paint loop.
//
/**
* Paints a region of a component
*
* @param paintingComponent Component to paint
* @param bufferComponent Component to obtain buffer for
* @param g Graphics to paint to
* @param x X-coordinate
* @param y Y-coordinate
* @param w Width
* @param h Height
*/
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)) {
g.setClip(x, y, w, h);
paintingComponent.paintToOffscreen(g, x, y, w, h, x + w, y + h);
}
}
/**
* Does a copy area on the specified region.
*
* @param clip Whether or not the copyArea needs to be clipped to the
* Component's bounds.
*/
void copyArea(JComponent c, Graphics g, int x, int y, int w, int h,
int deltaX, int deltaY, boolean clip) {
getPaintManager().copyArea(c, g, x, y, w, h, deltaX, deltaY, clip);
}
private java.util.List<RepaintListener> repaintListeners = new ArrayList<>(1);
private void addRepaintListener(RepaintListener l) {
repaintListeners.add(l);
}
private void removeRepaintListener(RepaintListener l) {
repaintListeners.remove(l);
}
/**
* Notify the attached repaint listeners that an area of the {@code c} component
* has been immediately repainted, that is without scheduling a repaint runnable,
* due to performing a "blit" (via calling the {@code copyArea} method).
*
* @param c the component
* @param x the x coordinate of the area
* @param y the y coordinate of the area
* @param w the width of the area
* @param h the height of the area
*/
void notifyRepaintPerformed(JComponent c, int x, int y, int w, int h) {
for (RepaintListener l : repaintListeners) {
l.repaintPerformed(c, x, y, w, h);
}
}
/**
* Invoked prior to any paint/copyArea method calls. This will
* be followed by an invocation of <code>endPaint</code>.
* <b>WARNING</b>: Callers of this method need to wrap the call
* in a <code>try/finally</code>, otherwise if an exception is thrown
* during the course of painting the RepaintManager may
* be left in a state in which the screen is not updated, eg:
* <pre>
* repaintManager.beginPaint();
* try {
* repaintManager.paint(...);
* } finally {
* repaintManager.endPaint();
* }
* </pre>
*/
void beginPaint() {
boolean multiThreadedPaint = false;
int paintDepth;
Thread currentThread = Thread.currentThread();
synchronized(this) {
paintDepth = this.paintDepth;
if (paintThread == null || currentThread == paintThread) {
paintThread = currentThread;
this.paintDepth++;
} else {
multiThreadedPaint = true;
}
}
if (!multiThreadedPaint && paintDepth == 0) {
getPaintManager().beginPaint();
}
}
/**
* Invoked after <code>beginPaint</code> has been invoked.
*/
void endPaint() {
if (isPaintingThread()) {
PaintManager paintManager = null;
synchronized(this) {
if (--paintDepth == 0) {
paintManager = getPaintManager();
}
}
if (paintManager != null) {
paintManager.endPaint();
synchronized(this) {
paintThread = null;
}
}
}
}
/**
* If possible this will show a previously rendered portion of
* a Component. If successful, this will return true, otherwise false.
* <p>
* WARNING: This method is invoked from the native toolkit thread, be
* very careful as to what methods this invokes!
*/
boolean show(Container c, int x, int y, int w, int h) {
return getPaintManager().show(c, x, y, w, h);
}
/**
* Invoked when the doubleBuffered or useTrueDoubleBuffering
* properties of a JRootPane change. This may come in on any thread.
*/
void doubleBufferingChanged(JRootPane rootPane) {
getPaintManager().doubleBufferingChanged(rootPane);
}
/**
* Sets the <code>PaintManager</code> that is used to handle all
* double buffered painting.
*
* @param paintManager The PaintManager to use. Passing in null indicates
* the fallback PaintManager should be used.
*/
void setPaintManager(PaintManager paintManager) {
if (paintManager == null) {
paintManager = new PaintManager();
}
PaintManager oldPaintManager;
synchronized(this) {
oldPaintManager = this.paintManager;
this.paintManager = paintManager;
paintManager.repaintManager = this;
}
if (oldPaintManager != null) {
oldPaintManager.dispose();
}
}
private synchronized PaintManager getPaintManager() {
if (paintManager == null) {
PaintManager paintManager = null;
if (doubleBufferingEnabled && !nativeDoubleBuffering) {
switch (bufferStrategyType) {
case BUFFER_STRATEGY_NOT_SPECIFIED:
Toolkit tk = Toolkit.getDefaultToolkit();
if (tk instanceof SunToolkit) {
SunToolkit stk = (SunToolkit) tk;
if (stk.useBufferPerWindow()) {
paintManager = new BufferStrategyPaintManager();
}
}
break;
case BUFFER_STRATEGY_SPECIFIED_ON:
paintManager = new BufferStrategyPaintManager();
break;
default:
break;
}
}
// null case handled in setPaintManager
setPaintManager(paintManager);
}
return paintManager;
}
private void scheduleProcessingRunnable(AppContext context) {
if (processingRunnable.markPending()) {
Toolkit tk = Toolkit.getDefaultToolkit();
if (tk instanceof SunToolkit) {
SunToolkit.getSystemEventQueueImplPP(context).
postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(),
processingRunnable));
} else {
Toolkit.getDefaultToolkit().getSystemEventQueue().
postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(),
processingRunnable));
}
}
}
/**
* PaintManager is used to handle all double buffered painting for
* Swing. Subclasses should call back into the JComponent method
* <code>paintToOffscreen</code> to handle the actual painting.
*/
static class PaintManager {
/**
* RepaintManager the PaintManager has been installed on.
*/
protected RepaintManager repaintManager;
boolean isRepaintingRoot;
/**
* Paints a region of a component
*
* @param paintingComponent Component to paint
* @param bufferComponent Component to obtain buffer for
* @param g Graphics to paint to
* @param x X-coordinate
* @param y Y-coordinate
* @param w Width
* @param h Height
* @return true if painting was successful.
*/
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.
boolean paintCompleted = false;
Image offscreen;
int sw = w + 1;
int sh = h + 1;
if (repaintManager.useVolatileDoubleBuffer() &&
(offscreen = getValidImage(repaintManager.
getVolatileOffscreenBuffer(bufferComponent, sw, sh))) != null) {
VolatileImage vImage = (java.awt.image.VolatileImage)offscreen;
GraphicsConfiguration gc = bufferComponent.
getGraphicsConfiguration();
for (int i = 0; !paintCompleted &&
i < RepaintManager.VOLATILE_LOOP_MAX; i++) {
if (vImage.validate(gc) ==
VolatileImage.IMAGE_INCOMPATIBLE) {
repaintManager.resetVolatileDoubleBuffer(gc);
offscreen = repaintManager.getVolatileOffscreenBuffer(
bufferComponent, sw, sh);
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) {
paintDoubleBuffered(paintingComponent, offscreen, g, x, y, w,
h);
paintCompleted = true;
}
return paintCompleted;
}
/**
* Does a copy area on the specified region.
*/
public void copyArea(JComponent c, Graphics g, int x, int y, int w,
int h, int deltaX, int deltaY, boolean clip) {
g.copyArea(x, y, w, h, deltaX, deltaY);
}
/**
* Invoked prior to any calls to paint or copyArea.
*/
public void beginPaint() {
}
/**
* Invoked to indicate painting has been completed.
*/
public void endPaint() {
}
/**
* Shows a region of a previously rendered component. This
* will return true if successful, false otherwise. The default
* implementation returns false.
*/
public boolean show(Container c, int x, int y, int w, int h) {
return false;
}
/**
* Invoked when the doubleBuffered or useTrueDoubleBuffering
* properties of a JRootPane change. This may come in on any thread.
*/
public void doubleBufferingChanged(JRootPane rootPane) {
}
/**
* Paints a portion of a component to an offscreen buffer.
*/
protected void paintDoubleBuffered(JComponent c, Image image,
Graphics g, int clipX, int clipY,
int clipW, int clipH) {
if (image instanceof VolatileImage && isPixelsCopying(c, g)) {
paintDoubleBufferedFPScales(c, image, g, clipX, clipY, clipW, clipH);
} else {
paintDoubleBufferedImpl(c, image, g, clipX, clipY, clipW, clipH);
}
}
private void paintDoubleBufferedImpl(JComponent c, Image image,
Graphics g, int clipX, int clipY,
int clipW, int clipH) {
Graphics osg = image.getGraphics();
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);
if (volatileBufferType != Transparency.OPAQUE
&& osg instanceof Graphics2D) {
final Graphics2D g2d = (Graphics2D) osg;
final Color oldBg = g2d.getBackground();
g2d.setBackground(c.getBackground());
g2d.clearRect(x, y, bw, bh);
g2d.setBackground(oldBg);
}
c.paintToOffscreen(osg, x, y, bw, bh, maxx, maxy);
g.setClip(x, y, bw, bh);
if (volatileBufferType != Transparency.OPAQUE
&& g instanceof Graphics2D) {
final Graphics2D g2d = (Graphics2D) g;
final Composite oldComposite = g2d.getComposite();
g2d.setComposite(AlphaComposite.Src);
g2d.drawImage(image, x, y, c);
g2d.setComposite(oldComposite);
} else {
g.drawImage(image, x, y, c);
}
osg.translate(x, y);
}
}
} finally {
osg.dispose();
}
}
private void paintDoubleBufferedFPScales(JComponent c, Image image,
Graphics g, int clipX, int clipY,
int clipW, int clipH) {
Graphics osg = image.getGraphics();
Graphics2D g2d = (Graphics2D) g;
Graphics2D osg2d = (Graphics2D) osg;
AffineTransform identity = new AffineTransform();
int bw = Math.min(clipW, image.getWidth(null));
int bh = Math.min(clipH, image.getHeight(null));
int x, y, maxx, maxy;
AffineTransform tx = g2d.getTransform();
double scaleX = tx.getScaleX();
double scaleY = tx.getScaleY();
double trX = tx.getTranslateX();
double trY = tx.getTranslateY();
boolean translucent = volatileBufferType != Transparency.OPAQUE;
Composite oldComposite = g2d.getComposite();
try {
for (x = clipX, maxx = clipX + clipW; x < maxx; x += bw) {
for (y = clipY, maxy = clipY + clipH; y < maxy; y += bh) {
// draw x, y, bw, bh
int pixelx1 = Region.clipRound(x * scaleX + trX);
int pixely1 = Region.clipRound(y * scaleY + trY);
int pixelx2 = Region.clipRound((x + bw) * scaleX + trX);
int pixely2 = Region.clipRound((y + bh) * scaleY + trY);
int pixelw = pixelx2 - pixelx1;
/**代码未完, 请加载全部代码(NowJava.com).**/