gpt4 book ai didi

java - 将 Swing 组件渲染到屏幕外缓冲区

转载 作者:搜寻专家 更新时间:2023-11-01 00:58:32 30 4
gpt4 key购买 nike

我有一个在 32 位 Windows 2008 Server 上运行的 Java (Swing) 应用程序,它需要将其输出呈现为离屏图像(然后由另一个 C++ 应用程序拾取以在其他地方呈现)。大多数组件都正确呈现,除了在刚刚失去焦点的组件被另一个组件遮挡的奇怪情况下,例如有两个 JComboBoxes 彼此靠近的情况下,如果用户与较低的 JComboBoxes 交互,然后单击上面的下拉框与另一个框重叠。

在这种情况下,失去焦点的组件在遮挡它的组件之后渲染,因此出现在输出的顶部。它在正常的 Java 显示中正确呈现(在主显示器上全屏运行),并且尝试更改有问题的组件的层没有帮助。

我正在使用自定义 RepaintManager 将组件绘制到屏幕外图像,我认为问题在于为每个相关组件调用 addDirtyRegion() 的顺序,但我想不出识别这种特定状态何时发生以防止它发生的好方法。对其进行黑客攻击,使刚刚失去焦点的对象不被重新绘制可以解决问题,但显然会导致更大的问题,即在所有其他正常情况下都不会重新绘制。

有没有办法以编程方式识别此状态,或重新排序以使其不会发生?

非常感谢,

尼克

[编辑]添加了一些代码作为示例:

重绘管理器和相关类:

class NativeObject {
private long nativeAddress = -1;

protected void setNativeAddress(long address) {
if ( nativeAddress != -1 ) {
throw new IllegalStateException("native address already set for " + this);
}
this.nativeAddress = address;
NativeObjectManager.getInstance().registerNativeObject(this, nativeAddress);
}
}

public class MemoryMappedFile extends NativeObject {
private ByteBuffer buffer;

public MemoryMappedFile(String name, int size)
{
setNativeAddress(create(name, size));
buffer = getNativeBuffer();
}

private native long create(String name, int size);

private native ByteBuffer getNativeBuffer();

public native void lock();

public native void unlock();

public ByteBuffer getBuffer() {
return buffer;
}
}

private static class CustomRepaintManager extends RepaintManager{
class PaintLog {
Rectangle bounds;
Component component;
Window window;

PaintLog(int x, int y, int w, int h, Component c) {
bounds = new Rectangle(x, y, w, h);
this.component = c;
}

PaintLog(int x, int y, int w, int h, Window win) {
bounds = new Rectangle(x, y, w, h);
this.window= win;
}
}

private MemoryMappedFile memoryMappedFile;
private BufferedImage offscreenImage;
private List<PaintLog> regions = new LinkedList<PaintLog>();
private final Component contentPane;
private Component lastFocusOwner;
private Runnable sharedMemoryUpdater;
private final IMetadataSource metadataSource;
private Graphics2D offscreenGraphics;
private Rectangle offscreenBounds = new Rectangle();
private Rectangle repaintBounds = new Rectangle();

public CustomRepaintManager(Component contentPane, IMetadataSource metadataSource) {
this.contentPane = contentPane;
this.metadataSource = metadataSource;
offscreenBounds = new Rectangle(0, 0, 1920, 1080);
memoryMappedFile = new MemoryMappedFile("SystemConfigImage", offscreenBounds.width * offscreenBounds.height * 3 + 1024);
offscreenImage = new BufferedImage(offscreenBounds.width, offscreenBounds.height, BufferedImage.TYPE_3BYTE_BGR);
offscreenGraphics = offscreenImage.createGraphics();

sharedMemoryUpdater = new Runnable(){
@Override
public void run()
{
updateSharedMemory();
}
};
}

private boolean getLocationRelativeToContentPane(Component c, Point screen) {
if(!c.isVisible()) {
return false;
}

if(c == contentPane) {
return true;
}

Container parent = c.getParent();
if(parent == null) {
System.out.println("can't get parent!");
return true;
}

if(!parent.isVisible()) {
return false;
}

while ( !parent.equals(contentPane)) {
screen.x += parent.getX();
screen.y += parent.getY();
parent = parent.getParent();

if(parent == null) {
System.out.println("can't get parent!");
return true;
}
if(!parent.isVisible()) {
return false;
}
}
return true;
}

protected void updateSharedMemory() {
if ( regions.isEmpty() ) return;

List<PaintLog> regionsCopy = new LinkedList<PaintLog>();

synchronized ( regions ) {
regionsCopy.addAll(regions);
regions.clear();
}

memoryMappedFile.lock();
ByteBuffer mappedBuffer = memoryMappedFile.getBuffer();
int imageDataSize = offscreenImage.getWidth() * offscreenImage.getHeight() * 3;
mappedBuffer.position(imageDataSize);

if ( mappedBuffer.getInt() == 0 ) {
repaintBounds.setBounds(0, 0, 0, 0);
} else {
repaintBounds.x = mappedBuffer.getInt();
repaintBounds.y = mappedBuffer.getInt();
repaintBounds.width = mappedBuffer.getInt();
repaintBounds.height = mappedBuffer.getInt();
}

for ( PaintLog region : regionsCopy ) {
if ( region.component != null && region.bounds.width > 0 && region.bounds.height > 0) {
Point regionLocation = new Point(region.bounds.x, region.bounds.y);
Point screenLocation = region.component.getLocation();
boolean isVisible = getLocationRelativeToContentPane(region.component, screenLocation);

if(!isVisible) {
continue;
}

if(region.bounds.x != 0 && screenLocation.x == 0 || region.bounds.y != 0 && screenLocation.y == 0){
region.bounds.width += region.bounds.x;
region.bounds.height += region.bounds.y;
}

Rectangle2D.intersect(region.bounds, offscreenBounds, region.bounds);

if ( repaintBounds.isEmpty() ){
repaintBounds.setBounds( screenLocation.x, screenLocation.y, region.bounds.width, region.bounds.height);
} else {
Rectangle2D.union(repaintBounds, new Rectangle(screenLocation.x, screenLocation.y, region.bounds.width, region.bounds.height), repaintBounds);
}

offscreenGraphics.translate(screenLocation.x, screenLocation.y);

region.component.paint(offscreenGraphics);

DataBufferByte byteBuffer = (DataBufferByte) offscreenImage.getData().getDataBuffer();
int srcIndex = (screenLocation.x + screenLocation.y * offscreenImage.getWidth()) * 3;
byte[] srcData = byteBuffer.getData();

int maxY = Math.min(screenLocation.y + region.bounds.height, offscreenImage.getHeight());
int regionLineSize = region.bounds.width * 3;

for (int y = screenLocation.y; y < maxY; ++y){
mappedBuffer.position(srcIndex);

if ( srcIndex + regionLineSize > srcData.length ) {
break;
}
if ( srcIndex + regionLineSize > mappedBuffer.capacity() ) {
break;
}
try {
mappedBuffer.put( srcData, srcIndex, regionLineSize);
}
catch ( IndexOutOfBoundsException e) {
break;
}
srcIndex += 3 * offscreenImage.getWidth();
}

offscreenGraphics.translate(-screenLocation.x, -screenLocation.y);
offscreenGraphics.setClip(null);

} else if ( region.window != null ){
repaintBounds.setBounds(0, 0, offscreenImage.getWidth(), offscreenImage.getHeight() );

offscreenGraphics.setClip(repaintBounds);

contentPane.paint(offscreenGraphics);

DataBufferByte byteBuffer = (DataBufferByte) offscreenImage.getData().getDataBuffer();
mappedBuffer.position(0);
mappedBuffer.put(byteBuffer.getData());
}
}

mappedBuffer.position(imageDataSize);
mappedBuffer.putInt(repaintBounds.isEmpty() ? 0 : 1);
mappedBuffer.putInt(repaintBounds.x);
mappedBuffer.putInt(repaintBounds.y);
mappedBuffer.putInt(repaintBounds.width);
mappedBuffer.putInt(repaintBounds.height);
metadataSource.writeMetadata(mappedBuffer);

memoryMappedFile.unlock();
}

@Override
public void addDirtyRegion(JComponent c, int x, int y, int w, int h) {
super.addDirtyRegion(c, x, y, w, h);
synchronized ( regions ) {
regions.add(new PaintLog(x, y, w, h, c));
}
SwingUtilities.invokeLater(sharedMemoryUpdater);
}

@Override
public void addDirtyRegion(Window window, int x, int y, int w, int h) {
super.addDirtyRegion(window, x, y, w, h);
synchronized (regions) {
regions.add(new PaintLog(x, y, w, h, window));
}
SwingUtilities.invokeLater(sharedMemoryUpdater);
}
}

出现问题的小组:

private static class EncodingParametersPanel extends JPanel implements ActionListener
{
private JLabel label1 = new JLabel();
private JComboBox comboBox1 = new JComboBox();

private JLabel label2 = new JLabel();
private JComboBox comboBox2 = new JComboBox();

private JLabel label3 = new JLabel();
private JComboBox comboBox3 = new JComboBox();

private JButton setButton = new JButton();

public EncodingParametersPanel()
{
super(new BorderLayout());

JPanel contentPanel = new JPanel(new VerticalFlowLayout());
JPanel formatPanel = new JPanel(new VerticalFlowLayout());

sdiFormatPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLoweredBevelBorder(), "Format"));

label1.setText("First Option:");
label2.setText("Second Option:");
label3.setText("Third OPtion:");

setButton.addActionListener(this);

formatPanel.add(label1);
formatPanel.add(comboBox1);
formatPanel.add(label2);
formatPanel.add(comboBox2);
formatPanel.add(label3);
formatPanel.add(comboBox3);

contentPanel.add(formatPanel);

contentPanel.add(setButton);

add(contentPanel);
}
}

使用此示例,如果用户与 comboBox2 交互,然后与 comboBox1 交互,comboBox1 的下拉菜单会与 comboBox2 重叠,但 comboBox2 会在其之上重绘。

最佳答案

我发现了一些可能有助于您所看到的东西。

在用于处理窗口重绘的updateSharedMemory 代码中,代码调用了contentPane.paint。这是最有可能的罪魁祸首,因为 Window 可能不是您的 contentPane。 JPopupMenu 的代码(由 JComboBox 使用)可以选择将弹出窗口显示为重量级组件。因此,Window 可能是 JComboBox 之一的弹出窗口。

此外,sharedMemoryUpdater 被安排在 EDT 上,一旦事件队列为空,它就会运行。因此,调用 addDirtyRegion 和调用 updateSharedMemory 之间可能存在延迟。在 updateSharedMemory 中调用了 region.component.paint。如果任何已排队的事件更改 component,则绘制调用的实际结果可能会有所不同。

测试的一些建议:

像这样创建 sharedMemoryUpdater:

    private Runnable scheduled = null;

sharedMemoryUpdater = Runnable {
public void run() {
scheduled = null;
updateSharedMemory();
}
}

然后,在addDirtyRegion

    if (scheduled == null) {
scheduled = sharedMemoryUpdater;
SwingUtilities.invokeLater(sharedMemoryUpdater);
}

这将减少 sharedMemoryUpdater 的调用次数(在我的测试中减少了 99%)。由于对 addDirtyRegion 的所有调用都应该在 EDT 上发生,因此您不需要在 scheduled 上进行同步,但添加不会有太大影响。

由于存在滞后,要处理的区域数量可能会变得非常大。在我的测试中,我看到它一度超过 400。

这些更改将减少操作区域列表所花费的时间,因为创建 1 个新列表比创建复制现有列表所需的所有条目更快。

private final Object regionLock = new Opject;
private List<PaintLog> regions = new LinkedList<PaintLog>();

// In addDirtyRegions()
synchronized(regionLock) {
regions.add(...);
}

// In updateSharedMemory()
List<PaintLog> regionsCopy;
List<PaintLog> tmp = new LinkedList<PaintLog>()
synchronized(regionLock) {
regionsCopy = regions;
regions = tmp;
}

关于java - 将 Swing 组件渲染到屏幕外缓冲区,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2908418/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com