8029455: [JLightweightFrame] support scaled painting

Reviewed-by: anthony, ant
This commit is contained in:
Sergey Bylokhov 2014-06-12 00:32:33 +04:00
parent 4a9ad9f15f
commit 652e8b5ffb
7 changed files with 331 additions and 67 deletions

View File

@ -29,12 +29,18 @@ import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.MenuBar;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import sun.awt.CGraphicsDevice;
import sun.awt.CGraphicsEnvironment;
import sun.awt.CausedFocusEvent;
import sun.awt.LightweightFrame;
import sun.java2d.SurfaceData;
import sun.lwawt.LWLightweightFramePeer;
import sun.lwawt.LWWindowPeer;
import sun.lwawt.PlatformWindow;
@ -72,11 +78,6 @@ public class CPlatformLWWindow extends CPlatformWindow {
return null;
}
@Override
public GraphicsDevice getGraphicsDevice() {
return null;
}
@Override
public SurfaceData getScreenSurface() {
return null;
@ -199,4 +200,24 @@ public class CPlatformLWWindow extends CPlatformWindow {
public long getLayerPtr() {
return 0;
}
@Override
public GraphicsDevice getGraphicsDevice() {
CGraphicsEnvironment ge = (CGraphicsEnvironment)GraphicsEnvironment.
getLocalGraphicsEnvironment();
LWLightweightFramePeer peer = (LWLightweightFramePeer)getPeer();
int scale = ((LightweightFrame)peer.getTarget()).getScaleFactor();
Rectangle bounds = ((LightweightFrame)peer.getTarget()).getHostBounds();
for (GraphicsDevice d : ge.getScreenDevices()) {
if (d.getDefaultConfiguration().getBounds().intersects(bounds) &&
((CGraphicsDevice)d).getScaleFactor() == scale)
{
return d;
}
}
// We shouldn't be here...
return ge.getDefaultScreenDevice();
}
}

View File

@ -31,6 +31,7 @@ import java.awt.Graphics;
import java.awt.Image;
import java.awt.MenuBar;
import java.awt.MenuComponent;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.peer.FramePeer;
@ -124,4 +125,48 @@ public abstract class LightweightFrame extends Frame {
* @see SunToolkit#ungrab(java.awt.Window)
*/
public abstract void ungrabFocus();
/**
* Returns the scale factor of this frame. The default value is 1.
*
* @return the scale factor
* @see #notifyDisplayChanged(int)
*/
public abstract int getScaleFactor();
/**
* Called when display of the hosted frame is changed.
*
* @param scaleFactor the scale factor
*/
public abstract void notifyDisplayChanged(int scaleFactor);
/**
* Host window absolute bounds.
*/
private int hostX, hostY, hostW, hostH;
/**
* Returns the absolute bounds of the host (embedding) window.
*
* @return the host window bounds
*/
public Rectangle getHostBounds() {
if (hostX == 0 && hostY == 0 && hostW == 0 && hostH == 0) {
// The client app is probably unaware of the setHostBounds.
// A safe fall-back:
return getBounds();
}
return new Rectangle(hostX, hostY, hostW, hostH);
}
/**
* Sets the absolute bounds of the host (embedding) window.
*/
public void setHostBounds(int x, int y, int w, int h) {
hostX = x;
hostY = y;
hostW = w;
hostH = h;
}
}

View File

@ -2108,7 +2108,7 @@ public final class SunGraphics2D
if (theData.copyArea(this, x, y, w, h, dx, dy)) {
return;
}
if (transformState >= TRANSFORM_TRANSLATESCALE) {
if (transformState > TRANSFORM_TRANSLATESCALE) {
throw new InternalError("transformed copyArea not implemented yet");
}
// REMIND: This method does not deal with missing data from the
@ -2129,8 +2129,25 @@ public final class SunGraphics2D
lastCAcomp = comp;
}
x += transX;
y += transY;
double[] coords = {x, y, x + w, y + h, x + dx, y + dy};
transform.transform(coords, 0, coords, 0, 3);
x = (int)Math.ceil(coords[0] - 0.5);
y = (int)Math.ceil(coords[1] - 0.5);
w = ((int)Math.ceil(coords[2] - 0.5)) - x;
h = ((int)Math.ceil(coords[3] - 0.5)) - y;
dx = ((int)Math.ceil(coords[4] - 0.5)) - x;
dy = ((int)Math.ceil(coords[5] - 0.5)) - y;
// In case of negative scale transform, reflect the rect coords.
if (w < 0) {
w *= -1;
x -= w;
}
if (h < 0) {
h *= -1;
y -= h;
}
Blit ob = lastCAblit;
if (dy == 0 && dx > 0 && dx < w) {

View File

@ -54,6 +54,7 @@ import javax.swing.RepaintManager;
import javax.swing.RootPaneContainer;
import javax.swing.SwingUtilities;
import sun.awt.DisplayChangedListener;
import sun.awt.LightweightFrame;
import sun.security.action.GetPropertyAction;
import sun.swing.SwingUtilities2.RepaintListener;
@ -80,6 +81,8 @@ public final class JLightweightFrame extends LightweightFrame implements RootPan
private BufferedImage bbImage;
private volatile int scaleFactor = 1;
/**
* {@code copyBufferEnabled}, true by default, defines the following strategy.
* A duplicating (copy) buffer is created for the original pixel buffer.
@ -90,7 +93,7 @@ public final class JLightweightFrame extends LightweightFrame implements RootPan
* by the lock (managed with the {@link LightweightContent#paintLock()},
* {@link LightweightContent#paintUnlock()} methods).
*/
private boolean copyBufferEnabled;
private static boolean copyBufferEnabled;
private int[] copyBuffer;
private PropertyChangeListener layoutSizeListener;
@ -103,6 +106,8 @@ public final class JLightweightFrame extends LightweightFrame implements RootPan
frame.updateClientCursor();
}
});
copyBufferEnabled = "true".equals(AccessController.
doPrivileged(new GetPropertyAction("swing.jlf.copyBufferEnabled", "true")));
}
/**
@ -144,7 +149,8 @@ public final class JLightweightFrame extends LightweightFrame implements RootPan
}
Point p = SwingUtilities.convertPoint(c, x, y, jlf);
Rectangle r = new Rectangle(p.x, p.y, w, h).intersection(
new Rectangle(0, 0, bbImage.getWidth(), bbImage.getHeight()));
new Rectangle(0, 0, bbImage.getWidth() / scaleFactor,
bbImage.getHeight() / scaleFactor));
if (!r.isEmpty()) {
notifyImageUpdated(r.x, r.y, r.width, r.height);
@ -198,6 +204,7 @@ public final class JLightweightFrame extends LightweightFrame implements RootPan
g.setBackground(getBackground());
g.setColor(getForeground());
g.setFont(getFont());
g.scale(scaleFactor, scaleFactor);
return g;
}
@ -221,7 +228,39 @@ public final class JLightweightFrame extends LightweightFrame implements RootPan
if (content != null) content.focusUngrabbed();
}
private void syncCopyBuffer(boolean reset, int x, int y, int w, int h) {
@Override
public int getScaleFactor() {
return scaleFactor;
}
@Override
public void notifyDisplayChanged(final int scaleFactor) {
if (scaleFactor != this.scaleFactor) {
if (!copyBufferEnabled) content.paintLock();
try {
if (bbImage != null) {
resizeBuffer(getWidth(), getHeight(), scaleFactor);
}
} finally {
if (!copyBufferEnabled) content.paintUnlock();
}
this.scaleFactor = scaleFactor;
}
if (getPeer() instanceof DisplayChangedListener) {
((DisplayChangedListener)getPeer()).displayChanged();
}
repaint();
}
@Override
public void addNotify() {
super.addNotify();
if (getPeer() instanceof DisplayChangedListener) {
((DisplayChangedListener)getPeer()).displayChanged();
}
}
private void syncCopyBuffer(boolean reset, int x, int y, int w, int h, int scale) {
content.paintLock();
try {
int[] srcBuffer = ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData();
@ -230,6 +269,11 @@ public final class JLightweightFrame extends LightweightFrame implements RootPan
}
int linestride = bbImage.getWidth();
x *= scale;
y *= scale;
w *= scale;
h *= scale;
for (int i=0; i<h; i++) {
int from = (y + i) * linestride + x;
System.arraycopy(srcBuffer, from, copyBuffer, from, w);
@ -241,7 +285,7 @@ public final class JLightweightFrame extends LightweightFrame implements RootPan
private void notifyImageUpdated(int x, int y, int width, int height) {
if (copyBufferEnabled) {
syncCopyBuffer(false, x, y, width, height);
syncCopyBuffer(false, x, y, width, height, scaleFactor);
}
content.imageUpdated(x, y, width, height);
}
@ -269,7 +313,8 @@ public final class JLightweightFrame extends LightweightFrame implements RootPan
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
notifyImageUpdated(clip.x, clip.y, clip.width, clip.height);
Rectangle c = contentPane.getBounds().intersection(clip);
notifyImageUpdated(c.x, c.y, c.width, c.height);
}
});
} finally {
@ -323,48 +368,37 @@ public final class JLightweightFrame extends LightweightFrame implements RootPan
content.paintLock();
}
try {
if ((bbImage == null) || (width != bbImage.getWidth()) || (height != bbImage.getHeight())) {
boolean createBB = true;
int newW = width;
int newH = height;
if (bbImage != null) {
int oldW = bbImage.getWidth();
int oldH = bbImage.getHeight();
if ((oldW >= newW) && (oldH >= newH)) {
createBB = false;
} else {
if (oldW >= newW) {
newW = oldW;
boolean createBB = (bbImage == null);
int newW = width;
int newH = height;
if (bbImage != null) {
int imgWidth = bbImage.getWidth() / scaleFactor;
int imgHeight = bbImage.getHeight() / scaleFactor;
if (width != imgWidth || height != imgHeight) {
createBB = true;
if (bbImage != null) {
int oldW = imgWidth;
int oldH = imgHeight;
if ((oldW >= newW) && (oldH >= newH)) {
createBB = false;
} else {
newW = Math.max((int)(oldW * 1.2), width);
}
if (oldH >= newH) {
newH = oldH;
} else {
newH = Math.max((int)(oldH * 1.2), height);
if (oldW >= newW) {
newW = oldW;
} else {
newW = Math.max((int)(oldW * 1.2), width);
}
if (oldH >= newH) {
newH = oldH;
} else {
newH = Math.max((int)(oldH * 1.2), height);
}
}
}
}
if (createBB) {
BufferedImage oldBB = bbImage;
bbImage = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB_PRE);
if (oldBB != null) {
Graphics g = bbImage.getGraphics();
try {
g.drawImage(oldBB, 0, 0, newW, newH, null);
} finally {
g.dispose();
oldBB.flush();
}
}
int[] pixels = ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData();
if (copyBufferEnabled) {
syncCopyBuffer(true, 0, 0, width, height);
pixels = copyBuffer;
}
content.imageBufferReset(pixels, 0, 0, width, height, bbImage.getWidth());
return;
}
}
if (createBB) {
resizeBuffer(newW, newH, scaleFactor);
return;
}
content.imageReshaped(0, 0, width, height);
@ -375,6 +409,18 @@ public final class JLightweightFrame extends LightweightFrame implements RootPan
}
}
private void resizeBuffer(int width, int height, int newScaleFactor) {
bbImage = new BufferedImage(width*newScaleFactor,height*newScaleFactor,
BufferedImage.TYPE_INT_ARGB_PRE);
int[] pixels= ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData();
if (copyBufferEnabled) {
syncCopyBuffer(true, 0, 0, width, height, newScaleFactor);
pixels = copyBuffer;
}
content.imageBufferReset(pixels, 0, 0, width, height,
width * newScaleFactor, newScaleFactor);
}
@Override
public JRootPane getRootPane() {
return rootPane;

View File

@ -85,31 +85,53 @@ public interface LightweightContent {
* {@code JLightweightFrame} calls this method to notify the client
* application that a new data buffer has been set as a content pixel
* buffer. Typically this occurs when a buffer of a larger size is
* created in response to a content resize event. The method reports
* a reference to the pixel data buffer, the content image bounds
* within the buffer and the line stride of the buffer. These values
* have the following correlation.
* created in response to a content resize event.
* <p>
* The {@code width} and {@code height} matches the size of the content
* The method reports a reference to the pixel data buffer, the content
* image bounds within the buffer and the line stride of the buffer.
* These values have the following correlation.
* The {@code width} and {@code height} matches the layout size of the content
* (the component returned from the {@link #getComponent} method). The
* {@code x} and {@code y} is the origin of the content, {@code (0, 0)}
* in the coordinate space of the content, appearing at
* {@code data[y * linestride + x]} in the buffer. All indices
* {@code data[(y + j) * linestride + (x + i)]} where
* {@code (0 <= i < width)} and {@code (0 <= j < height)} will represent
* valid pixel data, {@code (i, j)} in the coordinate space of the content.
* in the layout coordinate space of the content, appearing at
* {@code data[y * scale * linestride + x * scale]} in the buffer.
* A pixel with indices {@code (i, j)}, where {@code (0 <= i < width)} and
* {@code (0 <= j < height)}, in the layout coordinate space of the content
* is represented by a {@code scale^2} square of pixels in the physical
* coordinate space of the buffer. The top-left corner of the square has the
* following physical coordinate in the buffer:
* {@code data[(y + j) * scale * linestride + (x + i) * scale]}.
*
* @param data the content pixel data buffer of INT_ARGB_PRE type
* @param x the x coordinate of the image
* @param y the y coordinate of the image
* @param width the width of the image
* @param height the height of the image
* @param x the logical x coordinate of the image
* @param y the logical y coordinate of the image
* @param width the logical width of the image
* @param height the logical height of the image
* @param linestride the line stride of the pixel buffer
* @param scale the scale factor of the pixel buffer
*/
public void imageBufferReset(int[] data,
default public void imageBufferReset(int[] data,
int x, int y,
int width, int height,
int linestride);
int linestride,
int scale)
{
imageBufferReset(data, x, y, width, height, linestride);
}
/**
* The default implementation for #imageBufferReset uses a hard-coded value
* of 1 for the scale factor. Both the old and the new methods provide
* default implementations in order to allow a client application to run
* with any JDK version without breaking backward compatibility.
*/
default public void imageBufferReset(int[] data,
int x, int y,
int width, int height,
int linestride)
{
imageBufferReset(data, x, y, width, height, linestride, 1);
}
/**
* {@code JLightweightFrame} calls this method to notify the client

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2014, 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.
*
* 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.
*/
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
/**
* @test
* @bug 8029455
* @summary Tests that copyarea on offscreen images works as expected when
* scaled transform is set
* @run main ScaledCopyArea
*/
public final class ScaledCopyArea {
public static void main(final String[] args) {
final BufferedImage bi = new BufferedImage(100, 300,
BufferedImage.TYPE_INT_RGB);
final Graphics2D g = bi.createGraphics();
g.scale(2, 2);
g.setColor(Color.RED);
g.fillRect(0, 0, 100, 300);
g.setColor(Color.GREEN);
g.fillRect(0, 100, 100, 100);
g.copyArea(0, 100, 100, 100, 0, -100);
g.dispose();
for (int x = 0; x < 100; ++x) {
for (int y = 0; y < 100; ++y) {
final int actual = bi.getRGB(x, y);
final int exp = Color.GREEN.getRGB();
if (actual != exp) {
System.err.println("Expected:" + Integer.toHexString(exp));
System.err.println("Actual:" + Integer.toHexString(actual));
throw new RuntimeException("Test " + "failed");
}
}
}
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2014, 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.
*
* 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.
*/
import java.lang.reflect.InvocationTargetException;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
/**
* @test
* @bug 8029455
* @summary Swing should not hang if non-volatile image is used as a backbuffer.
* @run main/othervm -Dswing.volatileImageBufferEnabled=false HangNonVolatileBuffer
*/
public final class HangNonVolatileBuffer {
private static JFrame f;
public static void main(final String[] args)
throws InvocationTargetException, InterruptedException {
SwingUtilities.invokeAndWait(() -> {
f = new JFrame("JFrame");
f.setSize(300, 300);
f.setLocationRelativeTo(null);
f.setVisible(true);
});
SwingUtilities.invokeAndWait(() -> {
// flush the EDT
});
Thread.sleep(1000);
SwingUtilities.invokeAndWait(f::dispose);
}
}