Java AWT自定义CompositeContext和抗锯齿:在客户区外绘制时出现RasterFormatException

15
我正在尝试为 AWT Graphics2D 实现类似 SWT GC 的异或模式绘图。使用内置的 XORComposite 不是一个选项,因为它不能像 SWT 那样实现异或模式绘图。
SWT 的异或模式绘图通过二进制异或方式结合源颜色和目标颜色。AWT XORComposite(可通过 g2d.setXORMode(Color) 使用)使用一个恒定的异或颜色,该颜色与源颜色通过二进制异或结合,即目标颜色不影响最终颜色。
我所想到的唯一选择是编写自己的CompositeCompositeContext实现,以适当地结合源和目标。在阅读了一些资料后,我想出了这个简单的实现:(是的,我知道getPixel(...),setPixel(...)开销很大。在优化之前,我希望它能正常工作。)
private static class XorComposite implements Composite {

    public static XorComposite INSTANCE = new XorComposite();

    private XorContext context = new XorContext();

    @Override
    public CompositeContext createContext(ColorModel srcColorModel,
            ColorModel dstColorModel, RenderingHints hints) {
        return context;
    }

}

private static class XorContext implements CompositeContext {

    public XorContext() {
    }

    @Override
    public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
        int w = Math.min(src.getWidth(), dstIn.getWidth());
        int h = Math.min(src.getHeight(), dstIn.getHeight());

        int[] srcRgba = new int[4];
        int[] dstRgba = new int[4];

        for (int x = 0; x < w; x++) {
            for (int y = 0; y < h; y++) {
                src.getPixel(x, y, srcRgba);
                dstIn.getPixel(x, y, dstRgba);
                for (int i = 0; i < 3; i++) {
                    dstRgba[i] ^= srcRgba[i];
                }
                dstOut.setPixel(x, y, dstRgba);
            }
        }
    }

    @Override
    public void dispose() {
    }

}

当关闭抗锯齿时,此实现可以正常工作。如果启用了抗锯齿,只要我的绘图完全可见,即在我绘制的JPanel内部,它就可以工作。但是,如果一幅图跨越了JPanel的边界,则会引发RasterFormatException异常:

Exception in thread "AWT-EventQueue-0" java.awt.image.RasterFormatException: (y + height) is outside raster
    at sun.awt.image.IntegerInterleavedRaster.createWritableChild(IntegerInterleavedRaster.java:470)
    at sun.awt.image.IntegerInterleavedRaster.createChild(IntegerInterleavedRaster.java:514)
    at sun.java2d.pipe.GeneralCompositePipe.renderPathTile(GeneralCompositePipe.java:106)
    at sun.java2d.pipe.AAShapePipe.renderTiles(AAShapePipe.java:201)
    at sun.java2d.pipe.AAShapePipe.fillParallelogram(AAShapePipe.java:102)
    at sun.java2d.pipe.PixelToParallelogramConverter.fillRectangle(PixelToParallelogramConverter.java:322)
    at sun.java2d.pipe.PixelToParallelogramConverter.fill(PixelToParallelogramConverter.java:159)
    at sun.java2d.pipe.ValidatePipe.fill(ValidatePipe.java:160)
    at sun.java2d.SunGraphics2D.fill(SunGraphics2D.java:2422)
    at org.eclipse.gef4.graphics.examples.AwtXorTestPanel.paint(AwtXorTest.java:117)
    ... (irrelevant)

值得注意的是,我的Composite/CompositeContext没有抛出异常,但当尝试创建Raster对象并将其传递给我的CompositeContext时,AWT内部会抛出异常。不幸的是,当启用反锯齿时,PixelToParallelogramConverter只用于自定义合成操作。例如,内置的XORComposite使用本地方法进行绘制。我认为这是一个AWT bug,但我不确定。非常感谢您的帮助 :)
更新:正如Durandal所建议的那样,我使用java-6-sun和java-1.6.0-openjdk测试了代码。我发现OpenJDK会抛出异常,而Sun-JDK则不会。因此,我在OpenJDK bug跟踪器上报告了该bug。问题解决后,我将更新此问题。请访问相应的OpenJDK bug以获取有关当前进展的信息。
敬礼, Matthias
以下是一个示例程序,您可以在本地测试它:
/*******************************************************************************
 * Copyright (c) 2013 itemis AG and others.
 * 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Matthias Wienand (itemis AG) - initial API and implementation
 * 
 *******************************************************************************/
package org.eclipse.gef4.graphics.examples;

import java.awt.Color;

public class AwtXorTest extends JApplet {

    private static final long serialVersionUID = 1L;

    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setTitle("AWT XorMode Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JApplet applet = new AwtXorTest();
        applet.init();
        frame.getContentPane().add(applet);
        frame.pack();
        frame.setVisible(true);
    }

    @Override
    public void init() {
        JPanel panel = new AwtXorTestPanel();
        getContentPane().add(panel);
    }

}

class AwtXorTestPanel extends JPanel {

    private static class XorComposite implements Composite {

        public static XorComposite INSTANCE = new XorComposite();

        private XorContext context = new XorContext();

        @Override
        public CompositeContext createContext(ColorModel srcColorModel,
                ColorModel dstColorModel, RenderingHints hints) {
            return context;
        }

    }

    private static class XorContext implements CompositeContext {

        public XorContext() {
        }

        @Override
        public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
            int w = Math.min(src.getWidth(), dstIn.getWidth());
            int h = Math.min(src.getHeight(), dstIn.getHeight());

            int[] srcRgba = new int[4];
            int[] dstRgba = new int[4];

            for (int x = 0; x < w; x++) {
                for (int y = 0; y < h; y++) {
                    src.getPixel(x, y, srcRgba);
                    dstIn.getPixel(x, y, dstRgba);
                    for (int i = 0; i < 3; i++) {
                        dstRgba[i] ^= srcRgba[i];
                    }
                    dstOut.setPixel(x, y, dstRgba);
                }
            }
        }

        @Override
        public void dispose() {
        }

    }

    private static final long serialVersionUID = 1L;

    public AwtXorTestPanel() {
        setPreferredSize(new Dimension(640, 480));
    }

    @Override
    public void paint(Graphics graphics) {
        Graphics2D g2d = (Graphics2D) graphics;

        // comment out to see it working:
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        g2d.setComposite(XorComposite.INSTANCE);
        g2d.setColor(new Color(0, 255, 255)); // resulting color should be red
        g2d.fill(new Rectangle(100, 100, 500, 500));
    }

}

1
我在您的示例中没有收到任何异常(WinXP,Oracle JRE 1.6.0_41/1.7.0_15)。也许是您的Java出了问题,或者可能是驱动程序的问题(是的,我启用了抗锯齿功能)。 - Durandal
你的例子在我这里可以运行,而且我同意,我真的认为这是一个AWT的bug。没有AA,渲染管道会剪切目标矩形以确保它在可绘制空间内。你有没有看过AlphaComposite.Xor? - naugler
@Durandal,我正在使用Java 1.6在Ubuntu系统上工作。我认为这不是驱动程序问题,因为失败的代码是为自定义复合体生成光栅对象的代码。 - Matthias
@naugler,我知道AlphaComposite.Xor,但那不是我需要的。我想使用二进制异或来合并源颜色和目标颜色。这里有一个我想要实现的功能的示例:http://eclipse.org/articles/Article-SWT-graphics/SWT_graphics.html#XOR - Matthias
2个回答

1
注意:我已经有一段时间没有接触过栅格数据了。
看起来你可能正在访问栅格范围之外的像素。
栅格有 minX、minY,所以你的循环应该是这样的:
int srcMinX = src.getMinX();
int srcMinY = src.getMinY();
int dstInMinX = dstIn.getMinX();
int dstInMinY = dstIn.getMinY();
int dstOutMinX = dstOut.getMinX();
int dstOutMinY = dstOut.getMinY();

for (int x = 0; x < w; x++) {
    for (int y = 0; y < h; y++) {
        src.getPixel(x+srcMinX, y+srcMinY, srcRgba);
        dstIn.getPixel(x+dstInMinX, y+dstInMinY, dstRgba);
        for (int i = 0; i < 3; i++) {
            dstRgba[i] ^= srcRgba[i];
        }
        dstOut.setPixel(x+dstOutMinX, y+dstOutMinY, dstRgba);
    }
}

在这种情况下,minX和minY的值始终为零。相信我,在追踪错误时我很准确。如果我错过了如此明显的东西,那就太丢人了。无论如何,我刚刚测试了你的代码,它仍然以相同的错误消息失败。 - Matthias

0

g2d剪辑区域有多个框,因为其全尺寸光栅参考被抗锯齿框的光栅替换,所以会引发RasterFormatException异常。


请您详细说明一下吗?您从哪里获取了这些信息?在我看来,剪辑区域并不管理任何光栅对象,它们是由SurfaceData.getRaster()返回的一个派生对象。 - Matthias

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接