Skip to content

Commit

Permalink
JRE-604 [fps] frame's client area is one pixel beneath frame's borders
Browse files Browse the repository at this point in the history
  • Loading branch information
forantar committed Dec 22, 2017
1 parent 9ef00f0 commit 511e237
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 40 deletions.
7 changes: 7 additions & 0 deletions src/share/classes/java/awt/peer/WindowPeer.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,11 @@ public interface WindowPeer extends ContainerPeer {
* Instructs the peer to update the position of the security warning.
*/
void repositionSecurityWarning();

/**
* Returns the system insets (in the scale of the Window device) when available.
*
* @return the system insets or null
*/
default Insets getSysInsets() { return null; }
}
101 changes: 96 additions & 5 deletions src/share/classes/javax/swing/RepaintManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.VolatileImage;
import java.awt.peer.WindowPeer;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
Expand Down Expand Up @@ -1564,11 +1567,70 @@ public void doubleBufferingChanged(JRootPane rootPane) {
*/
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);
int clipW, int clipH)
{
Graphics gg = 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 (g instanceof SunGraphics2D) {
SunGraphics2D sg = (SunGraphics2D)gg;
if (sg.transformState == SunGraphics2D.TRANSFORM_TRANSLATESCALE) {
Point2D err = getInsetsRoundingError(sg);
double errX = err.getX();
double errY = err.getY();
if (errX != 0 || errY != 0) {
gg = sg = (SunGraphics2D)sg.create();

// 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);
}
}
}
try {
if (image instanceof VolatileImage && isPixelsCopying(c, g)) {
paintDoubleBufferedFPScales(c, image, gg, clipX, clipY, clipW, clipH);
} else {
paintDoubleBufferedImpl(c, image, gg, clipX, clipY, clipW, clipH);
}
} finally {
if (gg != g) g.dispose();
}
}

Expand Down Expand Up @@ -1613,6 +1675,35 @@ private void paintDoubleBufferedImpl(JComponent c, Image image,
}
}

/**
* 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)wnd.getPeer();
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) {
Expand Down
1 change: 1 addition & 0 deletions src/share/classes/sun/java2d/SunGraphics2D.java
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ public void constrain(int x, int y, int w, int h, Region region) {
// 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
x = constrainX = (int) transform.getTranslateX();
y = constrainY = (int) transform.getTranslateY();
w = Region.dimAdd(x, Region.clipScale(w, scaleX));
Expand Down
8 changes: 8 additions & 0 deletions src/windows/classes/sun/awt/windows/WWindowPeer.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ public class WWindowPeer extends WPanelPeer implements WindowPeer,
*/
private WindowListener windowListener;

private Insets sysInsets; // set from native updateInsets

/**
* Initialize JNI field IDs
*/
Expand Down Expand Up @@ -177,6 +179,7 @@ public void setResizable(boolean resizable) {
void initialize() {
super.initialize();

sysInsets = (Insets)insets_.clone();
updateInsets(insets_);

Font f = ((Window)target).getFont();
Expand Down Expand Up @@ -278,6 +281,11 @@ public void show() {
// state.
native void updateInsets(Insets i);

@Override
public Insets getSysInsets() {
return (Insets)sysInsets.clone();
}

static native int getSysMinWidth();
static native int getSysMinHeight();
static native int getSysIconWidth();
Expand Down
2 changes: 1 addition & 1 deletion src/windows/native/sun/java2d/d3d/D3DSurfaceData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ JNICALL Java_sun_java2d_d3d_D3DSurfaceData_initFlipBackbuffer
return JNI_FALSE;
}

pPeer->GetAlignedInsets(&r);
pPeer->GetInsets(&r);
d3dsdo->xoff = -r.left;
d3dsdo->yoff = -r.top;

Expand Down
4 changes: 2 additions & 2 deletions src/windows/native/sun/java2d/opengl/WGLSurfaceData.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ extern void AwtWindow_UpdateWindow(JNIEnv *env, jobject peer,
extern HBITMAP BitmapUtil_CreateBitmapFromARGBPre(int width, int height,
int srcStride,
int* imageData);
extern void AwtComponent_GetAlignedInsets(JNIEnv *env, jobject peer, RECT *insets);
extern void AwtComponent_GetInsets(JNIEnv *env, jobject peer, RECT *insets);

extern void
OGLSD_SetNativeDimensions(JNIEnv *env, OGLSDOps *oglsdo, jint w, jint h);
Expand Down Expand Up @@ -89,7 +89,7 @@ Java_sun_java2d_opengl_WGLSurfaceData_initOps(JNIEnv *env, jobject wglsd,
oglsdo->needsInit = JNI_TRUE;
if (peer != NULL) {
RECT insets;
AwtComponent_GetAlignedInsets(env, peer, &insets);
AwtComponent_GetInsets(env, peer, &insets);
oglsdo->xOffset = -insets.left;
oglsdo->yOffset = -insets.bottom;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ GDIWindowSurfaceData_GetWindow(JNIEnv *env, GDIWinSDOps *wsdo)
"GDIWindowSurfaceData_GetWindow: null component");
return (HWND) NULL;
}
comp->GetAlignedInsets(&wsdo->insets);
comp->GetInsets(&wsdo->insets);
window = comp->GetHWnd();
if (::IsWindow(window) == FALSE) {
J2dRlsTraceLn(J2D_TRACE_ERROR,
Expand Down
19 changes: 9 additions & 10 deletions src/windows/native/sun/windows/awt_Component.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ struct SetRectangularShapeStruct {
jint x1, x2, y1, y2;
jobject region;
};
// Struct for _GetAlignedInsets function
struct GetAlignedInsetsStruct {
// Struct for _GetInsets function
struct GetInsetsStruct {
jobject window;
RECT *insets;
};
Expand Down Expand Up @@ -989,8 +989,7 @@ void AwtComponent::Reshape(int x, int y, int w, int h)

// [tav] Handle the fact that an owned window is most likely positioned relative to its owner, and it may
// require pixel-perfect alignment. For that, compensate rounding errors (caused by converting from the device
// space to the integer user space and back) for the owner's origin and for the owner's client area origin
// (see Window::GetAlignedInsets).
// space to the integer user space and back) for the owner's origin and for the owner's client area origin.
if (IsTopLevel() && parent != NULL &&
(device->GetScaleX() > 1 || device->GetScaleY() > 1))
{
Expand Down Expand Up @@ -6449,11 +6448,11 @@ AwtComponent_GetHWnd(JNIEnv *env, jlong pData)
return p->GetHWnd();
}

static void _GetAlignedInsets(void* param)
static void _GetInsets(void* param)
{
JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);

GetAlignedInsetsStruct *gis = (GetAlignedInsetsStruct *)param;
GetInsetsStruct *gis = (GetInsetsStruct *)param;
jobject self = gis->window;

gis->insets->left = gis->insets->top =
Expand All @@ -6463,7 +6462,7 @@ static void _GetAlignedInsets(void* param)
JNI_CHECK_PEER_GOTO(self, ret);
AwtComponent *component = (AwtComponent *)pData;

component->GetAlignedInsets(gis->insets);
component->GetInsets(gis->insets);

ret:
env->DeleteGlobalRef(self);
Expand All @@ -6474,15 +6473,15 @@ static void _GetAlignedInsets(void* param)
* This method is called from the WGL pipeline when it needs to retrieve
* the insets associated with a ComponentPeer's C++ level object.
*/
void AwtComponent_GetAlignedInsets(JNIEnv *env, jobject peer, RECT *insets)
void AwtComponent_GetInsets(JNIEnv *env, jobject peer, RECT *insets)
{
TRY;

GetAlignedInsetsStruct *gis = new GetAlignedInsetsStruct;
GetInsetsStruct *gis = new GetInsetsStruct;
gis->window = env->NewGlobalRef(peer);
gis->insets = insets;

AwtToolkit::GetInstance().InvokeFunction(_GetAlignedInsets, gis);
AwtToolkit::GetInstance().InvokeFunction(_GetInsets, gis);
// global refs and mds are deleted in _UpdateWindow

CATCH_BAD_ALLOC;
Expand Down
4 changes: 0 additions & 4 deletions src/windows/native/sun/windows/awt_Component.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,6 @@ class AwtComponent : public AwtObject {
VERIFY(::SetRectEmpty(rect));
}

virtual void GetAlignedInsets(RECT* rect) {
VERIFY(::SetRectEmpty(rect));
}

BOOL IsVisible() { return m_visible;};

HDC GetDCFromComponent();
Expand Down
24 changes: 12 additions & 12 deletions src/windows/native/sun/windows/awt_Window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ jfieldID AwtWindow::sysYID;
jfieldID AwtWindow::sysWID;
jfieldID AwtWindow::sysHID;
jfieldID AwtWindow::windowTypeID;
jfieldID AwtWindow::sysInsetsID;

jmethodID AwtWindow::getWarningStringMID;
jmethodID AwtWindow::calculateSecurityWarningPositionMID;
Expand Down Expand Up @@ -1435,29 +1436,26 @@ BOOL AwtWindow::UpdateInsets(jobject insets)
jobject peerInsets = (env)->GetObjectField(peer, AwtPanel::insets_ID);
DASSERT(!safe_ExceptionOccurred(env));

jobject peerSysInsets = (env)->GetObjectField(peer, AwtWindow::sysInsetsID);
DASSERT(!safe_ExceptionOccurred(env));

int user_left = ScaleDownX(m_insets.left);
int user_top = ScaleDownY(m_insets.top);
int user_right = ScaleDownX(m_insets.right);
int user_bottom = ScaleDownY(m_insets.bottom);

// [tav] Align the insets in the device space with their transformed (and rounded to int) values in the user space.
// This will align the origin of the non-client area with its physical origin after transforming back to the
// device space. This will avoid gaps b/w the window's rendered content and the window's upper/left borders.
// However, even so the size of the non-client area, transformed back from the user space, may not exactly match
// the device size of the client area when the the window is resized natively (with mouse). In that case the
// right/bottom gaps will be filled with the window background color, which is configured in java and is
// expected to not contrast with the overall UI.
m_aligned_insets.left = ScaleUpX(user_left);
m_aligned_insets.top = ScaleUpY(user_top);
m_aligned_insets.right = ScaleUpX(user_right);
m_aligned_insets.bottom = ScaleUpY(user_bottom);

if (peerInsets != NULL) { // may have been called during creation
(env)->SetIntField(peerInsets, AwtInsets::topID, user_top);
(env)->SetIntField(peerInsets, AwtInsets::bottomID, user_bottom);
(env)->SetIntField(peerInsets, AwtInsets::leftID, user_left);
(env)->SetIntField(peerInsets, AwtInsets::rightID, user_right);
}
if (peerSysInsets != NULL) {
(env)->SetIntField(peerSysInsets, AwtInsets::topID, m_insets.top);
(env)->SetIntField(peerSysInsets, AwtInsets::bottomID, m_insets.bottom);
(env)->SetIntField(peerSysInsets, AwtInsets::leftID, m_insets.left);
(env)->SetIntField(peerSysInsets, AwtInsets::rightID, m_insets.right);
}
/* Get insets into the Inset object (if any) that was passed */
if (insets != NULL) {
(env)->SetIntField(insets, AwtInsets::topID, user_top);
Expand Down Expand Up @@ -3242,6 +3240,8 @@ Java_sun_awt_windows_WWindowPeer_initIDs(JNIEnv *env, jclass cls)
CHECK_NULL(AwtWindow::sysWID = env->GetFieldID(cls, "sysW", "I"));
CHECK_NULL(AwtWindow::sysHID = env->GetFieldID(cls, "sysH", "I"));

CHECK_NULL(AwtWindow::sysInsetsID = env->GetFieldID(cls, "sysInsets", "Ljava/awt/Insets;"));

AwtWindow::windowTypeID = env->GetFieldID(cls, "windowType",
"Ljava/awt/Window$Type;");

Expand Down
7 changes: 2 additions & 5 deletions src/windows/native/sun/windows/awt_Window.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ class AwtWindow : public AwtCanvas {
static jfieldID sysWID;
static jfieldID sysHID;

static jfieldID sysInsetsID;

static jfieldID windowTypeID;

static jmethodID getWarningStringMID;
Expand Down Expand Up @@ -104,10 +106,6 @@ class AwtWindow : public AwtCanvas {
VERIFY(::CopyRect(rect, &m_insets));
}

virtual void GetAlignedInsets(RECT* rect) {
VERIFY(::CopyRect(rect, &m_aligned_insets));
}

/* to make embedded frames easier */
virtual BOOL IsEmbeddedFrame() { return FALSE;}

Expand Down Expand Up @@ -272,7 +270,6 @@ class AwtWindow : public AwtCanvas {

RECT m_insets; /* a cache of the insets being used */
RECT m_old_insets; /* help determine if insets change */
RECT m_aligned_insets; /* transformed to user space and back to device scape */
POINT m_sizePt; /* the last value of WM_SIZE */
RECT m_warningRect; /* The window's warning banner area, if any. */
AwtFrame *m_owningFrameDialog; /* The nearest Frame/Dialog which owns us */
Expand Down

0 comments on commit 511e237

Please sign in to comment.