Transparency between SWT<->AWT Image Conversions
Asked Answered
A

2

6

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;
}
Aesthetically answered 8/10, 2013 at 20:54 Comment(12)
+1 for the question format. Do you really need to convert it to AWT and back again for resizing? Can't you just create a new SWT Image with your ImageData? Keeping in mind disposing of these resources, and maybe caching some results.Marsupial
@GGrec The other question suggests that all of the steps I am taking are necessary. As for memory advice: appreciated, but under control.Aesthetically
+1 Brilliant question. Unfortunately, I couldn't find a solution. Really looking forward to answers here.Streaky
On Mac OS X scaling SWT images using GC.drawImage works very well, not so good on Windows.Nedanedda
@Streaky Oh man, Baz. You're literally the best of the best! Who will I turn to now!?Aesthetically
@SouthpawHare Cheers, but I definitely don't know everything :D I tried getting it to work with just SWT, but I can't get the transparency working after scaling the image... I'm pretty sure that someone will be able to help here.Streaky
@Streaky Maybe if this question gets more than 1 view per hour. Questions like this hardly ever get any exposure at all =( ...got any SWT friends?Aesthetically
:D Unfortunately no, the other guys that are currently quite active are greg-449 and GGrec. But they have both already commented here...Streaky
You can describe the use case for me and the platform(s) it will run on. Maybe I can come up with something over the weekend.Streaky
I'm not very good at wording use cases. Basically, I want all images to display transparency with a white background, even after resizing. Showing up black is probably an issue.Aesthetically
@SouthpawHare What I meant is: Why do they need to be resized? Does the Shell resize or do you resize them before displaying them? Can you use external resources to resize the image or does it have to happen in the Java code? Are you displaying files that exist on the machine or are they online or contained in the jar?Streaky
@Streaky They are resized because it must support the browsing and selection of virtually any image, and then display it on a fairly fixed-size canvas.Aesthetically
S
2

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);
Streaky answered 12/10, 2013 at 8:44 Comment(7)
The main issue is not about making sure that the canvas can change size, although that does help. Rather, it is making sure that images whose background color would change when resized using my previous method would NOT with a different method. Can you confirm this to be true? Can you replicate my problem using my code, and fail to replicate it with your own?Aesthetically
@SouthpawHare Yes, I can. If you want me to verify it with any of you images, just upload one of them and post the link.Streaky
I found the Fluttershy image on deviantArt at th06.deviantart.net/fs71/PRE/f/2012/020/5/2/… . I didn't get any permission to use it, so I will not be re-uploading it, but you can use it from its origin there.Aesthetically
@SouthpawHare Ok, tested it with your image and it's working fine.Streaky
Okie dokey Loki, good enough for me. Thanks again as always, Mr. Baz!Aesthetically
So I'll be honest, I only just got around to testing this. Indeed, it does seem to work like a charm, so thanks again. A few notes, though: For one, canvas.getBounds and getSize don't seem to take into account the canvas border width, which you have to account for manually - you can see in your example pic that the very bottom of Fluttershy's tail is going off the bottom. It's also useful to check to not resize if the difference between the ratios is close to 0.000. Anyway, thanks again, Baz! (btw, are you the lightning biker from Divekick? I'm a fan of your work there too! ;P)Aesthetically
@SouthpawHare Who or what is the lightning biker from Divekick? :DStreaky
P
2

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();
Pollute answered 18/5, 2015 at 12:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.