SWT<->AWT图像转换之间的透明度

6
我创建了一个对话框,用户可以浏览图片,在画布上看到图像的预览。 图像被缩放,以保持其纵横比并适应框中。 我使用了此答案中找到的调整大小方法,包括将图像从SWT转换为AWT,执行调整大小,再将其从AWT转换回SWT,最后将其绘制在画布上。 由于这个过程在时间和处理能力方面非常昂贵,如果图像大小恰好正确,我会选择跳过调整大小步骤,因此不需要以任何方式进行转换。
处理带有alpha透明度的图像时会出现问题。 在某些情况下,首先转换透明度的图像将以黑色背景绘制在画布上。 大小与画布完全相同,因此不需要转换的相同图像的背景为白色。
但是,这也不总是如此。 一些具有透明背景的图像始终显示为白色,无论它们是否已被转换。 是什么原因导致一个带有透明背景的图像在SWT画布上绘制为一种颜色而不是另一种颜色? AWT转换如何影响它,如果我希望使其一致,我该怎么做?
这是完全从另一个来源获取的转换代码:
public static BufferedImage convertToAWT (ImageData data) {
    ColorModel colorModel = null;
    PaletteData palette = data.palette;
    if (palette.isDirect) {
        colorModel = new DirectColorModel(data.depth, palette.redMask, palette.greenMask, palette.blueMask);
        BufferedImage bufferedImage = new BufferedImage(colorModel, colorModel.createCompatibleWritableRaster(data.width, data.height),
                false, null);
        WritableRaster raster = bufferedImage.getRaster();
        int[] pixelArray = new int[3];
        for (int y = 0; y < data.height; y++) {
            for (int x = 0; x < data.width; x++) {
                int pixel = data.getPixel(x, y);
                RGB rgb = palette.getRGB(pixel);
                pixelArray[0] = rgb.red;
                pixelArray[1] = rgb.green;
                pixelArray[2] = rgb.blue;
                raster.setPixels(x, y, 1, 1, pixelArray);
            }
        }
        return bufferedImage;
    }
    else {
        RGB[] rgbs = palette.getRGBs();
        byte[] red = new byte[rgbs.length];
        byte[] green = new byte[rgbs.length];
        byte[] blue = new byte[rgbs.length];
        for (int i = 0; i < rgbs.length; i++) {
            RGB rgb = rgbs[i];
            red[i] = (byte) rgb.red;
            green[i] = (byte) rgb.green;
            blue[i] = (byte) rgb.blue;
        }
        if (data.transparentPixel != -1) {
            colorModel = new IndexColorModel(data.depth, rgbs.length, red, green, blue, data.transparentPixel);
        } else {
            colorModel = new IndexColorModel(data.depth, rgbs.length, red, green, blue);
        }
        BufferedImage bufferedImage = new BufferedImage(colorModel, colorModel.createCompatibleWritableRaster(data.width, data.height),
                false, null);
        WritableRaster raster = bufferedImage.getRaster();
        int[] pixelArray = new int[1];
        for (int y = 0; y < data.height; y++) {
            for (int x = 0; x < data.width; x++) {
                int pixel = data.getPixel(x, y);
                pixelArray[0] = pixel;
                raster.setPixel(x, y, pixelArray);
            }
        }
        return bufferedImage;
    }
}

public static ImageData convertToSWT (BufferedImage bufferedImage) {
    if (bufferedImage.getColorModel() instanceof DirectColorModel) {
        DirectColorModel colorModel = (DirectColorModel) bufferedImage.getColorModel();
        PaletteData palette = new PaletteData(colorModel.getRedMask(), colorModel.getGreenMask(), colorModel.getBlueMask());
        ImageData data = new ImageData(bufferedImage.getWidth(), bufferedImage.getHeight(), colorModel.getPixelSize(), palette);
        WritableRaster raster = bufferedImage.getRaster();
        int[] pixelArray = new int[3];
        for (int y = 0; y < data.height; y++) {
            for (int x = 0; x < data.width; x++) {
                raster.getPixel(x, y, pixelArray);
                int pixel = palette.getPixel(new RGB(pixelArray[0], pixelArray[1], pixelArray[2]));
                data.setPixel(x, y, pixel);
            }
        }
        return data;
    }
    else if (bufferedImage.getColorModel() instanceof IndexColorModel) {
        IndexColorModel colorModel = (IndexColorModel) bufferedImage.getColorModel();
        int size = colorModel.getMapSize();
        byte[] reds = new byte[size];
        byte[] greens = new byte[size];
        byte[] blues = new byte[size];
        colorModel.getReds(reds);
        colorModel.getGreens(greens);
        colorModel.getBlues(blues);
        RGB[] rgbs = new RGB[size];
        for (int i = 0; i < rgbs.length; i++) {
            rgbs[i] = new RGB(reds[i] & 0xFF, greens[i] & 0xFF, blues[i] & 0xFF);
        }
        PaletteData palette = new PaletteData(rgbs);
        ImageData data = new ImageData(bufferedImage.getWidth(), bufferedImage.getHeight(), colorModel.getPixelSize(), palette);
        data.transparentPixel = colorModel.getTransparentPixel();
        WritableRaster raster = bufferedImage.getRaster();
        int[] pixelArray = new int[1];
        for (int y = 0; y < data.height; y++) {
            for (int x = 0; x < data.width; x++) {
                raster.getPixel(x, y, pixelArray);
                data.setPixel(x, y, pixelArray[0]);
            }
        }
        return data;
    }
    return null;
}

@GGrec 另一个问题暗示我所采取的所有步骤都是必要的。至于内存建议:感谢,但已经掌控。 - Southpaw Hare
@SouthpawHare 感谢,但我绝对不是万事通 :D 我尝试只使用SWT使其工作,但在缩放图像后无法使透明度生效...我相信这里肯定有人能够提供帮助。 - Baz
很遗憾,目前非常活跃的其他人是greg-449GGrec。但他们已经在这里发表了评论... - Baz
你可以向我描述使用案例以及它将在哪些平台上运行。也许我能在周末想出一些解决方案。 - Baz
@Baz 这些图片被调整大小是因为它必须支持浏览和选择几乎任何图像,然后在一个相当固定大小的画布上显示它。 - Southpaw Hare
显示剩余7条评论
2个回答

2

好的,既然我认为我终于明白了你的要求,我决定发布一个答案。让我确认一下我的理解是否正确:

您想在应用程序中显示一个图片,该图片可以在某种可调整大小的小部件中显示。 图片应随其父级调整大小并保持透明度工作。

您可以使用Canvas,并使用GC#drawImage(Image image,int srcX,int srcY,int srcWidth,int srcHeight,int destX,int destY,int destWidth,int destHeight)将图像绘制到适当的大小。

要使用该函数,您需要Image的大小,Canvas的大小以及经过正确缩放(宽高比)的图像的大小。

以下是代码:

public static void main(String[] args)
{
    Display display = Display.getDefault();
    final Shell shell = new Shell(display);
    shell.setLayout(new GridLayout(1, false));

    /* Load the image and calculate size and ratio */
    final Image image = new Image(display, "settings.png");
    final Rectangle imageSize = image.getBounds();
    final double imageRatio = 1.0 * imageSize.width / imageSize.height;

    /* Define the canvas and set the background color */
    final Canvas canvas = new Canvas(shell, SWT.BORDER);
    canvas.setBackground(display.getSystemColor(SWT.COLOR_DARK_GRAY));
    canvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

    canvas.addListener(SWT.Paint, new Listener()
    {
        @Override
        public void handleEvent(Event e)
        {
            Rectangle canvasSize = canvas.getBounds();

            double canvasRatio = 1.0 * canvasSize.width / canvasSize.height;

            int newHeight;
            int newWidth;

            /* Determine scaled height and width of the image */
            if (canvasRatio > imageRatio)
            {
                newWidth = (int) (imageSize.width * (1.0 * canvasSize.height / imageSize.height));
                newHeight = (int) (canvasSize.height);
            }
            else
            {
                newWidth = (int) (canvasSize.width);
                newHeight = (int) (imageSize.height * (1.0 * canvasSize.width / imageSize.width));
            }

            /* Compute position such that the image is centered in the canvas */
            int top = (int) ((canvasSize.height - newHeight) / 2.0);
            int left = (int) ((canvasSize.width - newWidth) / 2.0);

            /* Draw the image */
            e.gc.drawImage(image, 0, 0, imageSize.width, imageSize.height, left, top, newWidth, newHeight);
        }
    });

    shell.pack();
    shell.open();
    while (!shell.isDisposed())
    {
        if (!display.readAndDispatch())
            display.sleep();
    }
    display.dispose();

    /* DISPOSE THE IMAGE !!! */
    image.dispose();
}

这是启动后的样子:

图片描述

调整大小后的样子如下:

图片描述


注意我没有时间在Windows上测试它,但我相信它可以工作。

它也适用于Windows!


编辑

添加以下行以启用抗锯齿:

e.gc.setAntialias(SWT.ON);
e.gc.setAdvanced(true);

主要问题不在于确保画布可以改变大小,尽管这确实有帮助。相反,问题在于确保使用不同的方法调整大小时,背景颜色会发生变化的图像不会出现在之前的方法中。您能确认这是正确的吗?您能否使用我的代码复制我的问题,并使用您自己的代码无法复制它? - Southpaw Hare
@SouthpawHare 是的,我可以。如果你想让我用你的任何一张图片来验证它,只需上传其中一张并发布链接即可。 - Baz
我在deviantArt上找到了Fluttershy的图片,网址为http://th06.deviantart.net/fs71/PRE/f/2012/020/5/2/fluttershy_vector_by_scrimpeh-d4mzicu.png。我没有得到使用它的许可,因此我不会重新上传它,但你可以从那里的原始位置使用它。 - Southpaw Hare
好的,差不多就这样吧。Baz先生,再次感谢您! - Southpaw Hare
所以说实话,我刚刚才测试了一下这个。确实,它似乎像魔法一样工作,所以再次感谢你。不过有几点需要注意:首先,canvas.getBounds和getSize似乎没有考虑到画布边框的宽度,你必须手动计算 - 你可以在你的示例图片中看到Fluttershy尾巴的底部正在超出底部。如果比率之间的差接近于0.000,则不调整大小也很有用。无论如何,再次感谢你,Baz!(顺便问一句,你是Divekick中的闪电摩托手吗?我也是你的粉丝!;P) - Southpaw Hare
显示剩余2条评论

2

现在回答有点晚了,但我最近遇到了类似的问题和经验,我想我的发现可能会帮助其他人。

最初的问题出在提供的代码上,它执行SWT->AWT和AWT->SWT转换。使用直接调色板时,完全不考虑透明度(alpha),但对于索引调色板,透明度是有作用的,这就是为什么一些图像有效而另一些则无效。

修复该代码以处理透明度相对简单,但有更好的解决方案,不需要通过AWT获取调整大小的图像。

如果您不关心转换后图像的抗锯齿性(平滑度),那么一个简单的解决方案是:

Image newImage = new Image(image.getDevice(),
                    image.getImageData().scaledTo(newWidth, newHeight));

如果您关心流畅性,那么解决方案几乎同样简单:

Image newImage = new Image(image.getDevice(), newWidth, newHeight);
GC gc = new GC(newImage);
gc.setAdvanced(true);
gc.setAntialias(SWT.ON);
gc.drawImage(image, 0, 0, origWidth, origHeight, 0, 0, newWidth, newHeight);
gc.dispose();

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