Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transparency between SWT<->AWT Image Conversions

I have created a dialog in which a user can browse for an image and then see a preview of the image drawn on a canvas. The image is scaled so that its aspect ratio is maintained while fitting in the box. I used the method of resizing found in this answer, which involves converting an image from SWT to AWT, performing the resize, converting back from AWT to SWT, and finally drawing it on the canvas. Since this process is very costly in terms of time and processing power, I elect to skip the resizing step if the image is exactly the correct size, and thus does not need to be transformed in any way.

The issue comes up when dealing with images with alpha transparency. In some cases, images that have transparency that are converted first are drawn on the canvas with a black background. A copy of the same image that has been sized to the exact size of the canvas, and thus is not converted, has a white background.

Fluttershy's background is so flaky and inconsistent.

However, this is also not always the case. Some images with transparent backgrounds will always show as white, whether they've been converted or not.

They've seen angrier days.

What causes an image with a transparent background to draw with one color over another in an SWT canvas? How does the AWT conversion affect it, and how can I cause it to become consistent if I so desire?

Here is the conversion code, taken in whole from another source:

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;
}
like image 616
Southpaw Hare Avatar asked Oct 08 '13 20:10

Southpaw Hare


2 Answers

Ok, since I think I finally understand your requirements, I decided to post an answer. Let me make sure that I understood it correctly:

You want to show an Image in your app in some sort of Widget that can be resized. The image should resize with its parent and keep transparency working.

Instead of resizing the image and displaying it in a Label or some other Widget, you can use a Canvas and paint the image to the appropriate size using GC#drawImage(Image image, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight).

To use that function, you need the size of the Image, the size of the Canvas and the size of a correctly scaled (aspect ratio) version of the image.

Here is the code:

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();
}

And this is what it looks like after starting:

enter image description here

and after resizing:

enter image description here


Note: I didn't have time to test it on Windows, but I'm fairly confident that it works.

It works on Windows as well!


EDIT:

Add these lines to enable antialiasing:

e.gc.setAntialias(SWT.ON);
e.gc.setAdvanced(true);
like image 153
Baz Avatar answered Oct 31 '22 19:10

Baz


It's a bit late for an answer now, but as I've just had a similar experience and issues, I thought my findings might help others.

The original problem is with the supplied code that does the SWT->AWT and AWT->SWT conversions. When using a direct palette, transparency (alpha) is not catered for at all, but it is for an indexed palette, and that's why some images work and some do not.

It's relatively simple to fix that code to cope with transparency, but there are better solutions that do not need to got via AWT to get a resized image.

If you don't care about anti-aliasing (smoothness) of the converted image then a simple solution is:

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

If you do care about smoothness then the solution is almost as simple:

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();
like image 21
Dave Hartnoll Avatar answered Oct 31 '22 17:10

Dave Hartnoll