JRE-681 [windows] direct drawing into frame graphics may have wrong translate

(cherry picked from commit ab6dee4c1fc453ad3cb5adb69fc243e550d184ae)

(cherry picked from commit 6ea1d45fd1)
This commit is contained in:
Anton Tarasov
2018-03-07 14:44:29 +03:00
committed by Vitaly Provodin
parent 8e739ea8f1
commit e25280ba09
4 changed files with 265 additions and 87 deletions

View File

@@ -1630,63 +1630,10 @@ public class RepaintManager
Graphics g, int clipX, int clipY,
int clipW, int clipH)
{
SunGraphics2D sg = (SunGraphics2D)g.create();
try {
// [tav] For the scaling graphics we need to compensate the toplevel insets rounding error
// to place [0, 0] of the client area in its correct device pixel.
if (sg.transformState == SunGraphics2D.TRANSFORM_TRANSLATESCALE) {
Point2D err = getInsetsRoundingError(sg);
double errX = err.getX();
double errY = err.getY();
if (errX != 0 || errY != 0) {
// save the current tx
AffineTransform tx = sg.transform;
// translate the constrain
Region constrainClip = sg.constrainClip;
Shape usrClip = sg.usrClip;
if (constrainClip != null) {
// SunGraphics2D.constrain(..) rounds down x/y, so to compensate we need to round up
int _errX = (int)Math.ceil(errX);
int _errY = (int)Math.ceil(errY);
if ((_errX | _errY) != 0) {
// drop everything to default
sg.constrainClip = null;
sg.usrClip = null;
sg.clipState = SunGraphics2D.CLIP_DEVICE;
sg.transform = new AffineTransform();
sg.setDevClip(sg.getSurfaceData().getBounds());
Region r = constrainClip.getTranslatedRegion(_errX, _errY);
sg.constrain(r.getLoX(), r.getLoY(), r.getWidth(), r.getHeight());
}
}
// translate usrClip
if (usrClip != null) {
if (usrClip instanceof Rectangle2D) {
Rectangle2D u = (Rectangle2D)usrClip;
u.setRect(u.getX() + errX, u.getY() + errY, u.getWidth(), u.getHeight());
} else {
usrClip = AffineTransform.getTranslateInstance(errX, errY).createTransformedShape(usrClip);
}
sg.transform = new AffineTransform();
sg.setClip(usrClip); // constrain clip is already valid
}
// finally translate the tx
AffineTransform newTx = AffineTransform.getTranslateInstance(errX - sg.constrainX, errY - sg.constrainY);
newTx.concatenate(tx);
sg.setTransform(newTx);
}
}
if (image instanceof VolatileImage && isPixelsCopying(c, g)) {
paintDoubleBufferedFPScales(c, image, sg, clipX, clipY, clipW, clipH);
} else {
paintDoubleBufferedImpl(c, image, sg, clipX, clipY, clipW, clipH);
}
} finally {
sg.dispose();
if (image instanceof VolatileImage && isPixelsCopying(c, g)) {
paintDoubleBufferedFPScales(c, image, g, clipX, clipY, clipW, clipH);
} else {
paintDoubleBufferedImpl(c, image, g, clipX, clipY, clipW, clipH);
}
}
@@ -1731,35 +1678,6 @@ public class RepaintManager
}
}
/**
* For the scaling graphics and a decorated toplevel as the destination,
* calculates the rounding error of the toplevel insets.
*
* @return the left/top insets rounding error, in device space
*/
private static Point2D getInsetsRoundingError(SunGraphics2D g) {
Point2D.Double err = new Point2D.Double(0, 0);
if (g.transformState >= SunGraphics2D.TRANSFORM_TRANSLATESCALE) {
Object dst = g.getSurfaceData().getDestination();
if (dst instanceof Frame && !((Frame)dst).isUndecorated() ||
dst instanceof Dialog && !((Dialog)dst).isUndecorated())
{
Window wnd = (Window)dst;
WindowPeer peer = (WindowPeer)AWTAccessor.getComponentAccessor().getPeer(wnd);
Insets sysInsets = peer != null ? peer.getSysInsets() : null;
if (sysInsets != null) {
Insets insets = wnd.getInsets();
// insets.left/top is a scaled down rounded value
// insets.left/top * tx.scale is a scaled up value (which contributes to graphics translate)
// sysInsets.left/top is the precise system value
err.x = sysInsets.left - insets.left * g.transform.getScaleX();
err.y = sysInsets.top - insets.top * g.transform.getScaleY();
}
}
}
return err;
}
private void paintDoubleBufferedFPScales(JComponent c, Image image,
Graphics g, int clipX, int clipY,
int clipW, int clipH) {

View File

@@ -371,7 +371,7 @@ public final class SunGraphics2D
// changes parameters according to the current scale and translate.
final double scaleX = transform.getScaleX();
final double scaleY = transform.getScaleY();
// [tav] rounding down affects aligning by insets in RepaintManager.paintDoubleBuffered
// [tav] rounding down affects aligning by insets in WWindowPeer.getGraphics
x = constrainX = (int)Math.floor(transform.getTranslateX());
y = constrainY = (int)Math.floor(transform.getTranslateY());
w = Region.dimAdd(x, Region.clipScale(w, scaleX));

View File

@@ -48,6 +48,9 @@ import java.awt.event.FocusEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.DataBufferInt;
import java.awt.peer.WindowPeer;
import java.beans.PropertyChangeEvent;
@@ -64,6 +67,8 @@ import sun.awt.Win32GraphicsConfig;
import sun.awt.Win32GraphicsDevice;
import sun.awt.Win32GraphicsEnvironment;
import sun.java2d.pipe.Region;
import sun.java2d.SunGraphics2D;
import sun.util.logging.PlatformLogger;
import static sun.java2d.SunGraphicsEnvironment.toUserSpace;
@@ -905,4 +910,83 @@ public class WWindowPeer extends WPanelPeer implements WindowPeer,
}
}
}
@Override
public Graphics getGraphics() {
Graphics g = super.getGraphics();
if (!(g instanceof SunGraphics2D)) return g;
SunGraphics2D sg = (SunGraphics2D)g;
// [tav] For the scaling graphics we need to compensate the toplevel insets rounding error
// to place [0, 0] of the client area in its correct device pixel.
if (sg.transformState == SunGraphics2D.TRANSFORM_TRANSLATESCALE) {
Point2D err = getInsetsRoundingError(sg);
double errX = err.getX();
double errY = err.getY();
if (errX != 0 || errY != 0) {
// save the current tx
AffineTransform tx = sg.transform;
// translate the constrain
Region constrainClip = sg.constrainClip;
Shape usrClip = sg.usrClip;
if (constrainClip != null) {
// SunGraphics2D.constrain(..) rounds down x/y, so to compensate we need to round up
int _errX = (int) Math.ceil(errX);
int _errY = (int) Math.ceil(errY);
if ((_errX | _errY) != 0) {
// drop everything to default
sg.constrainClip = null;
sg.usrClip = null;
sg.clipState = SunGraphics2D.CLIP_DEVICE;
sg.transform = new AffineTransform();
sg.setDevClip(sg.getSurfaceData().getBounds());
Region r = constrainClip.getTranslatedRegion(_errX, _errY);
sg.constrain(r.getLoX(), r.getLoY(), r.getWidth(), r.getHeight());
}
}
// translate usrClip
if (usrClip != null) {
if (usrClip instanceof Rectangle2D) {
Rectangle2D u = (Rectangle2D) usrClip;
u.setRect(u.getX() + errX, u.getY() + errY, u.getWidth(), u.getHeight());
} else {
usrClip = AffineTransform.getTranslateInstance(errX, errY).createTransformedShape(usrClip);
}
sg.transform = new AffineTransform();
sg.setClip(usrClip); // constrain clip is already valid
}
// finally translate the tx (in the device space, so via concatenate)
AffineTransform newTx = AffineTransform.getTranslateInstance(errX - sg.constrainX, errY - sg.constrainY);
newTx.concatenate(tx);
sg.setTransform(newTx);
}
}
return sg;
}
/**
* For the scaling graphics and a decorated toplevel as the destination,
* calculates the rounding error of the toplevel insets.
*
* @return the left/top insets rounding error, in device space
*/
private Point2D getInsetsRoundingError(SunGraphics2D g) {
Point2D.Double err = new Point2D.Double(0, 0);
if (g.transformState >= SunGraphics2D.TRANSFORM_TRANSLATESCALE && !isTargetUndecorated()) {
Insets sysInsets = getSysInsets();
if (sysInsets != null) {
Insets insets = ((Window)target).getInsets();
// insets.left/top is a scaled down rounded value
// insets.left/top * tx.scale is a scaled up value (which contributes to graphics translate)
// sysInsets.left/top is the precise system value
err.x = sysInsets.left - insets.left * g.transform.getScaleX();
err.y = sysInsets.top - insets.top * g.transform.getScaleY();
}
}
return err;
}
}

View File

@@ -0,0 +1,176 @@
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.util.concurrent.CountDownLatch;
/* @test
* bug JRE-681
* @summary Tests that drawing directly into frame's graphics doesn't shift relative to the frame's content.
* @author Anton Tarasov
* @requires (os.family == "windows")
* @run main/othervm -Dsun.java2d.uiScale.enabled=true
* -Dsun.java2d.uiScale=1.25
* -Dsun.java2d.d3d=false
* DrawOnFrameGraphicsTest
* @run main/othervm -Dsun.java2d.uiScale.enabled=true
* -Dsun.java2d.uiScale=1.5
* -Dsun.java2d.d3d=false
* DrawOnFrameGraphicsTest
* @run main/othervm -Dsun.java2d.uiScale.enabled=true
* -Dsun.java2d.uiScale=1.75
* -Dsun.java2d.d3d=false
* DrawOnFrameGraphicsTest
* @run main/othervm -Dsun.java2d.uiScale.enabled=true
* -Dsun.java2d.uiScale=2.0
* -Dsun.java2d.d3d=false
* DrawOnFrameGraphicsTest
* @run main/othervm -Dsun.java2d.uiScale.enabled=true
* -Dsun.java2d.uiScale=2.25
* -Dsun.java2d.d3d=false
* DrawOnFrameGraphicsTest
* @run main/othervm -Dsun.java2d.uiScale.enabled=true
* -Dsun.java2d.uiScale=2.5
* -Dsun.java2d.d3d=false
* DrawOnFrameGraphicsTest
* @run main/othervm -Dsun.java2d.uiScale.enabled=true
* -Dsun.java2d.uiScale=2.75
* -Dsun.java2d.d3d=false
* DrawOnFrameGraphicsTest
*/
// Note: -Dsun.java2d.d3d=false is the current IDEA mode.
public class DrawOnFrameGraphicsTest {
static final int F_WIDTH = 300;
static final int F_HEIGHT = 200;
static final Color FRAME_BG = Color.GREEN;
static final Color RECT_COLOR_1 = Color.RED;
static final Color RECT_COLOR_2 = Color.BLUE;
static final int RECT_SIZE = 20;
static final Rectangle rect = new Rectangle(F_WIDTH/2 - RECT_SIZE/2, F_HEIGHT/2 - RECT_SIZE/2, RECT_SIZE, RECT_SIZE);
static JFrame frame;
static JComponent comp;
static volatile CountDownLatch latch = new CountDownLatch(1);
static volatile boolean framePainted = false;
static Robot robot;
public static void main(String[] args) throws AWTException, InterruptedException {
try {
robot = new Robot();
} catch (AWTException e) {
throw new RuntimeException(e);
}
EventQueue.invokeLater(DrawOnFrameGraphicsTest::show);
Timer timer = new Timer(100, (event) -> {
Point loc;
try {
loc = frame.getContentPane().getLocationOnScreen();
} catch (IllegalComponentStateException e) {
latch.countDown();
return;
}
BufferedImage capture = robot.createScreenCapture(
new Rectangle(loc.x + 50, loc.y + 50, 1, 1));
Color pixel = new Color(capture.getRGB(0, 0));
framePainted = FRAME_BG.equals(pixel);
latch.countDown();
return;
});
timer.setRepeats(false);
latch.await(); // wait for ACTIVATED
latch = new CountDownLatch(1);
//noinspection Duplicates
while (!framePainted) {
timer.start();
latch.await();
latch = new CountDownLatch(1);
}
//
// Draw on the frame
//
EventQueue.invokeLater(DrawOnFrameGraphicsTest::draw);
latch.await();
//
// Take the capture of the colored rect with some extra space
//
Point pt = comp.getLocationOnScreen();
BufferedImage capture = robot.createScreenCapture(
new Rectangle(pt.x + rect.x - 5, pt.y + rect.y - 5,
rect.width + 10, rect.height + 10));
//
// Test RECT_COLOR_1 is fully covered with RECT_COLOR_2
//
boolean hasRectColor2 = false;
for (int x=0; x<capture.getWidth(); x++) {
for (int y = 0; y < capture.getHeight(); y++) {
Color pix = new Color(capture.getRGB(x, y));
if (RECT_COLOR_1.equals(pix)) {
throw new RuntimeException("Test FAILED!");
}
hasRectColor2 |= RECT_COLOR_2.equals(pix);
}
}
if (!hasRectColor2) {
throw new RuntimeException("Test FAILED!");
}
System.out.println("Test PASSED");
}
static void show() {
frame = new JFrame("frame");
frame.setLocationRelativeTo(null);
frame.setBackground(FRAME_BG);
frame.getContentPane().setBackground(FRAME_BG);
comp = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(RECT_COLOR_1);
g2d.fill(rect);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(F_WIDTH, F_HEIGHT);
}
};
frame.add(comp);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setAlwaysOnTop(true);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowActivated(WindowEvent e) {
latch.countDown();
}
});
frame.setVisible(true);
}
static void draw() {
Graphics2D g2d = (Graphics2D)frame.getGraphics();
g2d.setColor(RECT_COLOR_2);
Point fpt = frame.getLocationOnScreen();
Point cpt = comp.getLocationOnScreen();
cpt.translate(-fpt.x, -fpt.y);
g2d.fill(new Rectangle(cpt.x + rect.x, cpt.y + rect.y, rect.width, rect.height));
latch.countDown();
}
}